Overview
Overview
A Farcaster Snap is an interactive embed inside a cast. It renders as a card in the feed and can be multi-page, stateful, and dynamic. Snaps are defined by a JSON response served by an external server. The Farcaster client renders the JSON — it never executes arbitrary code.
Snaps are the evolution of Frames: richer components, multi-page flows, dynamic content, and the same server-driven model.
Example interaction:
- A cast embed points to a URL that implements the snap protocol
- The client GETs that URL, signaling snap support. The server responds with a JSON SnapResponse
- The client renders the
uitree using the component catalog - The user interacts with field components (
input,slider,switch,toggle_group) — values are stored locally - The user taps a
buttonelement whoseon.pressis bound to asubmitaction - The client collects all field values and POSTs a signed payload to the
targetURL - The server returns a new SnapResponse — the client renders it as the next page
- Repeat
Content Negotiation
The snap media type is application/vnd.farcaster.snap+json. Clients and servers use
HTTP headers (Accept, Content-Type, Vary, and Link) to signal Snap support and
so the same URL can serve snap JSON or fallback content. See
HTTP Headers for details.
Authentication
Main page: Authentication
Snap POST requests use JSON Farcaster Signatures (JFS) for authentication.
Response Structure
Valid snap responses have roughly this shape:
{
"version": "2.0",
"theme": { "accent": "purple" },
"effects": ["confetti"],
"ui": {
"root": "page",
"elements": {
"page": {
"type": "stack",
"props": {},
"children": ["header", "guess", "submit"]
},
"header": {
"type": "item",
"props": { "title": "Daily Wordle", "description": "Attempt 3 of 6" }
},
"guess": {
"type": "input",
"props": { "name": "word", "label": "Your guess", "maxLength": 5 }
},
"submit": {
"type": "button",
"props": { "label": "Submit", "variant": "primary" },
"on": {
"press": {
"action": "submit",
"params": { "target": "https://wordle.example.com/guess" }
}
}
}
}
}
}
Top-Level Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
version | "2.0" | Yes | Spec version | |
theme | object | No | { accent: "purple" } | Theme configuration |
theme.accent | PaletteColor | No | "purple" | Accent color for buttons, progress bars, etc. |
effects | string[] | No | Visual effects applied on render. See Effects | |
ui | json-render Spec | Yes | The UI tree |
The ui Field
The ui field is a json-render Spec — a flat element map
with typed components, props, and event bindings.
| Field | Type | Required | Description |
|---|---|---|---|
ui.root | string | Yes | ID of the root element |
ui.elements | Record<string, UIElement> | Yes | Flat map of all elements by ID |
ui.state | Record<string, unknown> | No | Initial state for the json-render state store |
Element Structure
Every element in ui.elements follows this shape:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Component name (see Elements) |
props | object | No | Component-specific properties (omit or use {} if none) |
children | string[] | No | Child element IDs (for containers and action slots) |
on | object | No | Event bindings — on.press triggers an action when a button is tapped |
POST Payload
When a submit action fires, the client sends a JFS-signed envelope containing:
| Field | Type | Description |
|---|---|---|
fid | number | Deprecated — same as user.fid; kept for compatibility |
inputs | Record<string, value> | Field values keyed by component name prop |
timestamp | number | Unix timestamp in seconds |
audience | string | Origin of the intended recipient; must match server origin |
user | { fid: number } | User taking the action (canonical identity) |
surface | discriminated union | Interaction context — see Surfaces |
The POST is sent to the URL in the button’s submit action (params.target). The
audience must match the snap server’s origin. Use different targets for different
buttons—for example distinct paths or query strings—so your server can tell which action
ran.
Input values by field type:
| Component | Value sent |
|---|---|
input | string |
slider | number |
switch | boolean |
toggle_group (single) | string |
toggle_group (multiple) | string[] |
Broken Snaps
If the snap URL is unreachable, returns invalid JSON, or fails schema validation:
- The embed does not render in the feed
- The cast displays normally with the snap URL shown as plain text in the cast body
- The client may cache the last valid first page and show it with a "stale" indicator, at its discretion
If a submit action fails (timeout, server error, or invalid JSON response):
- The client stays on the current page — it is never replaced with a blank screen or error page
- An inline error is shown on the current page: "Something went wrong. Tap to retry."
- The user can retry the same button tap, or close/navigate away from the snap
Navigation
There is no client-managed back button. Navigation is server-driven.
If a snap wants "go back" functionality, it includes a button with a submit action
that POSTs to the server, and the server returns the appropriate previous page. The
server is responsible for maintaining navigation state.
Versioning
The version field is required on every response. Clients must check this field.
- If the version is supported, render normally
- If the version is newer than the client supports, show a fallback: the snap name/URL with a message "Update Farcaster to view this snap"
- Snaps should target the lowest version that supports their component types
Validator (@farcaster/snap)
Runtime validation lives in
@farcaster/snap
(pkgs/snap). The package validates snap JSON against the schema.
pnpm --filter @farcaster/snap test
Hono-oriented HTTP wiring (registerSnapHandler) is in
@farcaster/snap-hono.