Guides
Itsy Guides

Managed apps

Add custom server-side behavior with a local executable that exchanges JSON with Itsy.

How apps fit

Itsy Server serves the HTML and handles form protection. Your app handles its logic and returns data for the template. The bundled Contact app uses this model; Blog has its own built-in content editor.

The app exchanges JSON with Itsy. The browser receives HTML.

Custom apps run as managed local executables. You provide their code and storage behavior.

Register an app

Build an executable that implements the contract below. Place its files in itsy/apps/example/ with an app.json manifest:

{
  "name": "example",
  "version": "0.1.0",
  "protocol": 2,
  "executable": {
    "darwin-arm64": "example-darwin-arm64",
    "linux-amd64": "example-linux-amd64"
  }
}

Include a binary for your development computer and server. Add the app to your existing configuration:

{"apps": {"example": {"mount": "/example"}}}

Create site/example/index.html. Itsy launches the app and passes requests below this mount to it. Restart dev after rebuilding the executable or changing app settings.

Requests and template data

A custom app receives GET requests for pages that exist under its mount. A form POST needs {csrf} and sends its fields as flat JSON:

{"name": "Sam"}

Repeated fields become arrays of strings. Itsy strips the mount from the path: /example/greet calls /greet. Its own CSRF field is removed before forwarding.

Return application/json. A 200 response can supply template values:

{"data": {"message": "Hello, Sam"}}

The page can display {example.data.message}. App data is escaped and never interpreted as template code.

The response accepts six keys:

data
An object of template values.
invalid
Field names mapped to validation messages.
error
A failure message for the page.
flash
Named messages for the rendered page.
redirect
A mount-relative path, such as thank-you.
session
A cookie instruction, described below.

Use 422 with invalid for field errors, or 503 with error for an operational failure. A successful redirect becomes a browser 303. Other responses become a generic unavailable page.

Forms are limited to 32 KiB and JSON responses to 1 MiB. Unknown envelope keys are rejected; only session accepts an explicit null.

Launch and handshake

The process receives three inherited file descriptors:

3
A bound loopback TCP listener for your HTTP server.
4
The launch configuration as JSON.
5
A parent-liveness pipe. Shut down when it reaches EOF.
{
  "Name": "example",
  "Token": "<private bearer token>",
  "Storage": "<private storage directory>",
  "CapabilityURL": "",
  "CapabilityToken": ""
}

The last two values are empty for custom apps. Read configuration from these descriptors rather than environment variables.

Require Authorization: Bearer <Token> on every request. Answer GET /_flea/handshake with status 200:

{"name": "example", "version": "0.1.0", "protocol": 2}

The name and protocol must match the package. The handshake path retains the Flea spelling.

App-owned sessions

Your app owns session state. Itsy passes the opaque session token in X-Flea-Session. On a successful response, set or rotate it with:

{"session": {"token": "<your opaque token>", "max_age": 86400}}

Use a cookie-safe token of 16 to 512 characters and a lifetime of up to 30 days. Omit session to leave it unchanged, or return session: null to clear it.

Itsy handles the host-only, Secure, HttpOnly cookie. An app session does not grant admin access.

The provided storage directory survives deployments. It is private and excluded from site backups, so arrange backups for your app's data.