Building a Snap
There are several ways to create a Farcaster Snap, from AI-assisted generation to manual implementation.
Agent Skill
If you use a coding agent like Claude Code, you can ask it to install a skill that generates snaps from natural language:
install the farcaster-snap skill from https://docs.farcaster.xyz/snap/1.0/SKILL.md
make me a Farcaster Snap poll asking users to pick their favorite variety of mole
The skill will:
- Read the full snap spec
- Generate valid snap code
- Deploy it to a live URL
This is the fastest way to go from idea to working snap.
Template (Hono)
The snap-template/ directory is a starter project using Hono with
the @farcaster/snap-hono package:
# From the repo root
cp -r snap-template my-snap
cd my-snap
pnpm install
Edit src/index.ts to implement your snap logic:
import { Hono } from "hono";
import { registerSnapHandler } from "@farcaster/snap-hono";
const app = new Hono();
registerSnapHandler(app, async (ctx) => {
if (ctx.action.type === "get") {
return {
version: "1.0",
theme: { accent: "purple" },
ui: {
root: "page",
elements: {
page: {
type: "stack",
props: {},
children: ["title", "body", "action"],
},
title: {
type: "text",
props: { content: "My Snap", weight: "bold" },
},
body: {
type: "text",
props: { content: "Hello world" },
},
action: {
type: "button",
props: { label: "Refresh", variant: "primary" },
on: {
press: {
action: "submit",
params: { target: "https://my-snap.com/" },
},
},
},
},
},
};
}
// Handle POST interactions
const { fid, inputs, button_index } = ctx.action;
// ... your logic here
});
Run locally:
SKIP_JFS_VERIFICATION=1 pnpm dev # http://localhost:3003
Testing
Use the Emulator to test your snap. Enter your snap's URL and interact with it -- the emulator signs messages automatically, so no signature bypass is needed.
Deploying
Snaps can be deployed anywhere that serves HTTP. Common options:
- Vercel -- works with the Hono template out of the box
- Any Node.js host -- the Hono template includes a standalone server
Set SNAP_PUBLIC_BASE_URL to your deployment origin (no trailing slash) so button
target URLs resolve correctly.
After deploying, verify your snap works:
curl -sS -H 'Accept: application/vnd.farcaster.snap+json' https://your-snap-url.com/
You should get valid JSON with content type application/vnd.farcaster.snap+json.
Common pitfalls
Missing .js on local imports
The template is an ESM project ("type": "module"). All relative imports must include
the .js extension, even though the source files are .ts:
// ✅ correct
import { foo } from "./foo.js";
// ❌ wrong — breaks on deploy with `500 FUNCTION_INVOCATION_FAILED`
import { foo } from "./foo";
Omitting the extension is a silent trap: tsx accepts bare imports in local dev, and the
TypeScript "bundler" module resolution used to accept them at typecheck too — but the
Vercel Edge / Node ESM runtime enforces the extension, so every route returns
500 FUNCTION_INVOCATION_FAILED after deploy. The current template's tsconfig.json
uses "moduleResolution": "NodeNext" so pnpm build (tsc --noEmit) now catches this
at build time.
Bare package imports (hono, @farcaster/snap, etc.) do not need an extension.