Examples
Real-world examples of SnapResponse payloads showing common patterns.
Paginated Gallery / Multiple Buttons
A multi-page gallery with Previous / Next navigation using query parameters.
The handler reads ?idx=N from the URL and returns different content for each page.
Buttons use action: "submit" with a target URL that includes the next or previous
index.
You can use this same approach if you have multiple buttons on the same page that do
different things: give each button a different target using GET params. Then the
server will know which button was clicked.
First page (?idx=0)
Alpha
1 of 5
After tapping Next (?idx=1)
Bravo
2 of 5
Handler code
import type { SnapFunction } from "@farcaster/snap";
const items = ["Alpha", "Bravo", "Charlie", "Delta", "Echo"];
function snapBaseUrlFromRequest(request: Request): string {
const fromEnv = process.env.SNAP_PUBLIC_BASE_URL?.trim();
if (fromEnv) return fromEnv.replace(/\/$/, "");
const forwardedHost = request.headers.get("x-forwarded-host");
const hostHeader = request.headers.get("host");
const host = (forwardedHost ?? hostHeader)?.split(",")[0].trim();
const isLoopback =
host !== undefined && /^(localhost|127\.0\.0\.1|\[::1\]|::1)(:\d+)?$/.test(host);
const forwardedProto = request.headers.get("x-forwarded-proto");
const proto = forwardedProto
? forwardedProto.split(",")[0].trim().toLowerCase()
: isLoopback
? "http"
: "https";
if (host) return `${proto}://${host}`.replace(/\/$/, "");
return `http://localhost:${process.env.PORT ?? "3003"}`.replace(/\/$/, "");
}
const snap: SnapFunction = async (ctx) => {
const url = new URL(ctx.request.url);
const idx = Math.max(
0,
Math.min(items.length - 1, parseInt(url.searchParams.get("idx") ?? "0", 10) || 0),
);
const prev = Math.max(0, idx - 1);
const next = Math.min(items.length - 1, idx + 1);
const base = snapBaseUrlFromRequest(ctx.request);
return {
version: "1.0",
theme: { accent: "blue" },
ui: {
root: "page",
elements: {
page: { type: "stack", props: {}, children: ["title", "counter", "nav"] },
title: { type: "text", props: { content: items[idx], weight: "bold" } },
counter: {
type: "text",
props: { content: `${idx + 1} of ${items.length}`, size: "sm" },
},
nav: {
type: "stack",
props: { direction: "horizontal" },
children: ["prev-btn", "next-btn"],
},
"prev-btn": {
type: "button",
props: { label: "Previous" },
on: {
press: { action: "submit", params: { target: `${base}/?idx=${prev}` } },
},
},
"next-btn": {
type: "button",
props: { label: "Next", variant: "primary" },
on: {
press: { action: "submit", params: { target: `${base}/?idx=${next}` } },
},
},
},
},
};
};
Collaborative Wordle
A word game with a text input and submit button.
First page (feed card)
Daily Wordle · Day 12
1,247 guesses today · Attempt 4/6
{
"version": "1.0",
"theme": { "accent": "green" },
"ui": {
"root": "page",
"elements": {
"page": {
"type": "stack",
"props": {},
"children": ["title", "guess", "meta", "submit-btn"]
},
"title": {
"type": "text",
"props": { "content": "Daily Wordle · Day 12", "weight": "bold" }
},
"guess": {
"type": "input",
"props": {
"name": "guess",
"label": "Your guess",
"placeholder": "Type 5-letter word...",
"maxLength": 5
}
},
"meta": {
"type": "text",
"props": { "content": "1,247 guesses today · Attempt 4/6", "size": "sm" }
},
"submit-btn": {
"type": "button",
"props": { "label": "Submit guess", "variant": "primary" },
"on": {
"press": {
"action": "submit",
"params": { "target": "https://wordle.example.com/guess" }
}
}
}
}
}
}
Response after submitting a guess
Daily Wordle · Day 12
Your guess has been submitted!
The crowd’s most popular guess will be locked in at 6pm
{
"version": "1.0",
"theme": { "accent": "green" },
"ui": {
"root": "page",
"elements": {
"page": {
"type": "stack",
"props": {},
"children": ["title", "result", "meta", "open-btn"]
},
"title": {
"type": "text",
"props": { "content": "Daily Wordle · Day 12", "weight": "bold" }
},
"result": {
"type": "text",
"props": {
"content": "Your guess has been submitted!",
"align": "center"
}
},
"meta": {
"type": "text",
"props": {
"content": "The crowd's most popular guess will be locked in at 6pm",
"size": "sm"
}
},
"open-btn": {
"type": "button",
"props": { "label": "Open full game" },
"on": {
"press": {
"action": "open_mini_app",
"params": { "target": "https://wordle.example.com/app" }
}
}
}
}
}
}
This or That
A voting snap with a choice group and progress bars for results.
First page (feed card)
Startup dilemmas
by @dwr.eth · 3.1k voted
{
"version": "1.0",
"theme": { "accent": "blue" },
"ui": {
"root": "page",
"elements": {
"page": {
"type": "stack",
"props": {},
"children": ["title", "meta", "vote", "vote-btn"]
},
"title": {
"type": "text",
"props": { "content": "Startup dilemmas", "weight": "bold" }
},
"meta": {
"type": "text",
"props": { "content": "by @dwr.eth · 3.1k voted", "size": "sm" }
},
"vote": {
"type": "toggle_group",
"props": {
"name": "vote",
"orientation": "vertical",
"options": ["Move fast, break things", "Move deliberately, build trust"]
}
},
"vote-btn": {
"type": "button",
"props": { "label": "Vote", "variant": "primary" },
"on": {
"press": {
"action": "submit",
"params": { "target": "https://example.com/thisorthat/vote" }
}
}
}
}
}
}
Response after voting
Startup dilemmas
Move fast, break things
38%
Move deliberately, build trust
62% · 3,102 votes
{
"version": "1.0",
"theme": { "accent": "blue" },
"ui": {
"root": "page",
"elements": {
"page": {
"type": "stack",
"props": {},
"children": [
"title",
"opt-a-label",
"opt-a-bar",
"opt-b-label",
"opt-b-bar",
"actions"
]
},
"title": {
"type": "text",
"props": { "content": "Startup dilemmas", "weight": "bold" }
},
"opt-a-label": {
"type": "text",
"props": { "content": "Move fast, break things", "size": "sm" }
},
"opt-a-bar": {
"type": "progress",
"props": { "value": 38, "max": 100, "label": "38%" }
},
"opt-b-label": {
"type": "text",
"props": { "content": "Move deliberately, build trust", "size": "sm" }
},
"opt-b-bar": {
"type": "progress",
"props": { "value": 62, "max": 100, "label": "62% · 3,102 votes" }
},
"actions": {
"type": "stack",
"props": { "direction": "horizontal", "gap": "sm" },
"children": ["next-btn", "share-btn"]
},
"next-btn": {
"type": "button",
"props": { "label": "Next question", "variant": "primary" },
"on": {
"press": {
"action": "submit",
"params": { "target": "https://example.com/thisorthat/next" }
}
}
},
"share-btn": {
"type": "button",
"props": { "label": "Share results", "icon": "share" },
"on": {
"press": {
"action": "open_url",
"params": { "target": "https://example.com/thisorthat/share/abc123" }
}
}
}
}
}
}