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": "1.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 | "1.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 | Yes | Component-specific properties (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 | Farcaster user ID |
inputs | Record<string, value> | Field values keyed by component name prop |
button_index | number | Button index (0-based) |
timestamp | number | Unix timestamp in seconds |
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.