Getting Started
A folder is a URL, and its +page.wisp is the page: an optional block of Rust, then markup. Templates compile to Rust, and the app, styles and static files become one binary.
Install and Run
You need Rust 1.89 or later.
cargo install --git https://wisp.ar0.eu/ --tag v1.0.0-pre.3 wisp-web
wisp new my-app # --template demo|minimal|api
cd my-app
wisp dev # http://127.0.0.1:3000
Other systems, prerequisites for Windows, Linux and macOS, and the version history are on Changelog and Downloads.
wisp new asks a few questions. Press Enter for the defaults or add -y.
wisp dev rebuilds as you edit and keeps your $state. Alt+Shift+W opens the devtools with the routes table.
A Page
src/routes/+page.wisp:
---
let name = cx.query_or("name", "world".to_string());
let count = 0;
---
<title>Hello, ${name}</title>
<meta name="description" content="A greeting from the server, with a button that counts clicks in the browser.">
<h1>Hello, ${name}!</h1>
<button on:click="count++">Clicked {:count} times</button>
The --- block is Rust that runs per request. ${name} renders on the server. {:count} is JavaScript state in the browser. A literal let alone on its line is browser state, with nothing computed per request. With JavaScript off, the server's HTML still works.
Data and a Form
A model lives in src/db.rs, and its pub items are in every route file:
// src/db.rs
#[model]
struct Todo {
#[validate(len = 1..=100)]
text: String,
}
pub static TODOS: Table<Todo> = Table::saved();
An action handles the form, and fields writes a labelled input per parameter:
---
#[action]
fn add(todo: Todo) {
TODOS.add(todo)?;
}
let count = TODOS.len();
---
<title>Your todo list (${count})</title>
<meta name="description" content="Add todos with a form; each one is kept in a saved table.">
<h1>Todos</h1>
<form action="?/add" fields><button>Add</button></form>
{#each TODOS.all() as todo}
<p>${todo.text}</p>
{/each}
A bad value gives a 422 with each error next to its input and the typed text kept, with no JavaScript. There is no use line: a prelude brings in Cx, Table, Email, redirect, error and the rest.
Files
src/main.rs wisp::main!(); (generated; leave it)
src/app.html shell with %wisp.head% %wisp.body% (optional)
src/app.css | app.scss served at /_app/app.css (Tailwind if it imports it; Sass, no Node)
src/hooks.rs fn init() once; fn before(cx) every request
src/db.rs models and tables; its pub items are in every route file
src/components/Card.wisp <Card title=${x}>...</Card>
src/routes/.../+page.wisp page: one file, a --- Rust block (load, actions, endpoints), then markup
src/routes/.../+layout.wisp wraps pages below; must <slot />
src/routes/.../+template.wisp like a layout, fresh on each navigation
src/routes/.../+error.wisp error page
src/routes/.../+server.rs endpoints: fn get/post/...; beside a page, a browser gets the page
src/routes/.../+page.md Markdown page
static/ served at /
Folders: blog static, [slug] param, [[lang]] optional, [...rest] rest, [id=int] digits, (group) not in the URL. More in Files and routes.
Check, Test, Build
wisp check # templates, routes, accessibility lints
wisp test # your tests (wisp test --browser drives a real browser)
wisp fmt # format
wisp build # one release binary
Other builds: wisp build --static for any static host, wisp deploy init docker for a container, wisp build --target cloudflare|deno|vercel|netlify|node|bun|lambda for edge and serverless. See Deploying.
Next
Read the Quick Start, then the Tutorial. With an AI agent, use AGENTS.md or claude mcp add wisp -- wisp mcp.
All Pages
Start
Design
- Design and files
- Pages and templates
- Tooling, images and translations
- Template syntax and styles
- Actions, forms and UI
- Hooks, state and streaming
- Runtime and build
- CLI, dev loop and security
- AI agents and editors
- Parity features
- Cookies and sign-in
Browser Code
- Browser code
- Server values and client blocks
- Islands and loading
- The router and forms
- Server functions and errors
- PWA and the dev loop
- Client components and state
- With JavaScript off
- Component libraries
APIs
- APIs and platforms
- Input, output and errors
- Auth, limits, jobs and config
- OpenAPI and tests
- Where rows are kept
Data and Auth
Deploy and Run
- Deploying
- Edge and serverless targets
- Serve extras
- Testing and mixing with Rust code
- Logs, metrics and traces
- Profiling
Hosting
- Hosting
- One page per host: VPS, Docker, Fly.io, Render, Railway, Cloudflare, Deno Deploy, Vercel, Netlify, AWS Lambda, Bun, Node, GitHub Pages, Cloud Run, Azure
- Tor onion services
Reference
Project
Is This Page Useful?