Deploy a handler
Serve a route from an already-built WebAssembly component. A handler is a
function reached by an HTTP route — you declare it
in project.cfg, validate the manifest, then sync, and the sync step validates the
component blob and activates it against the site policy.
To build a component from scratch, see Write your first handler. To use the host bindings from guest code, see Use handler bindings. To run the same kind of component invoked by name instead of behind a route, see Deploy & invoke a function.
Before you start
- A component built to the
wasm32-wasip2target that exportswasi:http/incoming-handler. Sync rejects a component without this export. - The component file reachable from your project root (here,
dist/api.wasm). - A server built with the
handlersfeature. - The site policy enabled (
handlers.enabledon the site) and itsallow_importscovering every import you request — set this in step 3.syncdoes not set it, so a fresh site refuses a handler deployment until you do.
1. Declare the handler in project.cfg
Add the handler to the routing.handlers list. Each entry names a route pattern,
the allowed methods, the component file, and the host imports it may use (sql,
wasi:keyvalue, wasi:blobstore, wasi:messaging, invoke, plus wasi:http /
wasi:io, which every handler gets):
routing: (
handlers: [
( route: "/api/**", component: "dist/api.wasm",
methods: ["GET", "POST"],
imports: ["sql", "wasi:keyvalue"] ),
],
),
A component receives only the imports it declares here, and only those the site
also grants. Unlisted interfaces (for example wasi:filesystem) are refused even
when named.
A handler can also call a sibling top-level function in-process: grant it
invoke and add an invoke_targets allowlist naming the functions it may reach
(deny by default, * wildcards allowed). See
Deploy & invoke a function and
Use handler bindings.
2. Validate the manifest
Check the config shape and route table before you deploy:
boatramp validate
project.cfg: routing OK (1 handler: /api/** [GET, POST])
validate checks the manifest. The component blob itself — parseability, the
wasi:http/incoming-handler export, and the import allowlist — is validated at
sync.
3. Enable handlers on the site
The route you declared ships in the deployment, but a deployment that ships
handlers is refused at activation unless the site permits them. That gate is the
site’s handlers.enabled policy — a piece of
site config separate from the deployment,
and sync never sets it. Skip this step and the sync below fails with:
activation refused: deployment ships handlers/consumers but the site has them disabled
Enable it once with boatramp handlers. List the interfaces your handlers import
(allow_imports must be a superset of every handler’s imports); the example
/api/** handler above imports sql and wasi:keyvalue:
boatramp handlers enable --site my-site --allow sql --allow wasi:keyvalue
boatramp handlers show --site my-site prints the current policy; handlers disable
turns it back off; handlers allow/deny <import>… adjust the allowlist. An import a
handler requests but the site doesn’t grant is refused at activation — see
Use handler bindings.
Or, declaratively, fold the same policy into an apply.cfg config.handlers block
and run boatramp apply — it sets the site policy then deploys + activates, in the
right order, so one command does the whole thing:
sites: [(
name: "my-site",
path: "./dist",
routing: ( handlers: [( route: "/api/**", component: "dist/api.wasm",
methods: ["GET", "POST"], imports: ["sql", "wasi:keyvalue"] )] ),
config: ( handlers: ( enabled: true, allow_imports: ["sql", "wasi:keyvalue"] ) ),
)],
4. Sync the deployment
Upload the component and activate it:
boatramp sync ./dist --site my-site
validated dist/api.wasm — exports wasi:http/incoming-handler, imports OK
activated my-site -> 7f3a2b2c — handler /api/**
If the component requests an import the site does not allow, sync rejects the deployment and the previous one stays live.
5. Call the route
curl https://my-site.example/api/health
{"status":"ok"}
A method outside the handler’s methods list returns 405; a path outside the
route pattern falls through to rewrites, then static content.
Reference
- Route and import fields: project.cfg schema.
- Using bindings from guest code: Use handler bindings.
- Build a handler end to end: Write your first handler.