llms.txt

Examples

Real-world examples of SnapResponse payloads showing common patterns.

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" }
          }
        }
      }
    }
  }
}