Cover photo

Not Your Average Frames Tutorial

A whistle-stop tour of building a Farcaser Frame


Let’s build a Farcaster Frame!

After reading this tutorial, you will be have an understanding of the main components driving a Frame and see them in action.

As content for this tutorial I assembled a little trivia-style game: MVP or Not MVP. The objective is to guess whether a product or service were MVPs when publicly announced. Players select one of two options and at the end receive a score based on the number of correct answers.

Frames are rather simple primitives and after reading the official spec of a Frame, you might get a sense of what they are and why they unlock a new level of interactivity inside social feeds. They extend the OpenGraph protocol by adding dynamic content on top of what was meant to standardise static metadata about web pages.

How do you build one?

Some familiarity with Next.js is encouraged, although you could simply clone the repo, run it and tinker with the code, a lot of less obvious conventions are imposed by the framework.

Along the way, I will explain some fundamentals and lessons learned.


This demo has the following components:

  1. The Frame front-end - to render URLs with Frame meta tags

  2. The Frame Server - to handle taps on buttons and advance the game

  3. Persistent Storage for the game state - to store the game across sessions

  4. A Farcaster Hub - to validate FIDs and message data

High-level system diagram of components in the Frame powering the game demo.

When Farcaster clients try to embed the content of an URL, they look at all the <meta> tags in the <head> . If clients find a set of tags that correctly define a Frame, they render it, otherwise they fall back to the OG protocol.

The first rendered frame is cached, so the best practice is to not have dynamic data on it.

When users tap any of the Frame buttons, Farcaster makes a request to the Frame Server to perform some logic and return the content of another Frame.

The URL of the Frame Server is defined by the <meta property="fc:frame:post_url" content="..." /> tag.

Step 1 - Create a first static Frame

This is almost trivial given the generateMetadata functionality in Next.js 14, because inside this function we only need to return an object with the right keys.

Here’s my example:

Source from /app/page.tsx

fc:frame:post_url and fc:frame:image point to the Frame Server, which for this Next.js app is hosted on the same machine as the front-end.

This Frame contains a single button, with default actions. Tapping it makes a POST request to /api/start with info about the user who tapped and index of the button.

Step 2 - Responding to the button

This logic sits server-side on the Frame Server, so it has access the persistent storage service to initiate the game.

Source at /app/api/start/route.ts

The Request object is key here, because it contains a payload sent by Farcaster with an encoded version of the message, the FID of the user who triggered the action and the URL of the frame. These can be validated by a Farcaster HUB. I use Neynar in this demo. The validation logic itself is fairly straight-forward and lifted from the official Frames demo. See it inside app/frames.ts.

To separate display from business logic, the endpoint completes with a 302 redirect to the page which renders a new Frame and new buttons.

The first screen had a single button, so there’s no need to check its index, because it can only have one function: start the game.

After the redirect to /start a frame renderes with the first level:

Screenshot of a Frame of one level

So far we covered 3 paths:

  • / (root path)

  • /api/start

  • /start

These 3 paths cover the core loop of any Frame:

  1. Render

  2. Interact

  3. Call Frame Server

  4. Repeat

The 1, 2, 3 cycle is repeated as long as there is content to display inside this frame.

To chain multiple levels together, I split the game in 3 stages:

  1. /start - the first screen which starts the game

  2. /next - levels 1 through 5

  3. /over - the final screen

Each stage is comprised of an API endpoint plus a corresponding Next.js page.

Diving into /next as an example, this is the API endpoint:

Source from /app/api/next/route.ts

Assuming the game did not end, the code to render the next level Frame looks like this:

Source from /app/next/page.tsx

The API endpoint passes query params to the Next.js page it redirects to. Here, the game id is sent so the page knows which image to display.

Next.js runs the above code client-side, so we won’t have access to server-side functionality like Storage.

Step 3 - Rendering Images

So far the app takes advantage of mostly Next.js boilerplate code, while following the Frame specs. The final challenge is rendering images for the frames. Fortunately, there are quite a few options available:

  • Satori for generating SVGs from HTML

  • Sharp for rasterizing SVGs to PNGs

Here's a glance over the code:

Source app/api/images/level/route.tsx

Step 4 - Recap

The cycle is complete once the image for a new frame is rendered. Forks in the logic path can be added and the flow expanded path this point. We can now build navigation for a tiny, focused app.

That was it! This minimal game emphasizes the best parts of building Farcaster Frames.

Constraints are great for innovation as they provide focus, encourage creativity, foster resourcefulness, create urgency, x promote efficiency. These limitations encourage innovators to concentrate on specific problems, think creatively, utilize resources effectively, act swiftly, and seek efficient solutions. The key lies in approaching them with a positive and creative mindset, turning challenges into opportunities for breakthroughs.

In times when the UI stack for building web apps is getting more and more complex, Frames feel like a fresh take, back to first principles of interaction, natively supported on most platforms.

If Next.js is too much and because most of the content is rendered on the server, you can choose almost any other framework, regardless of language. I was tempted to replicate this demo in Flask, FastAPI or Hono.


Play "MVP or Not MVP" here:

Checkout the repo:


Why did you not use frames.js, onchainkit or

Frames.js , and onchainkit are great libraries that abstract away a lot of boilerplate code, while introducing good practices. I decided to not use them for the purpose of this demo, to highlight some fundamentals that app frameworks like Next.js already include.

My intention was to keep dependencies to a minimum, to focus on the core.

What is the full tech-stack used in the demo?

  • Next.js 14

  • React

  • Vercel Cloud hosting + CI/CD

  • Satori + Sharp

  • Neynar for access to a Farcaster Hub

  • TypeScript

  • yarn

Where can I find other interesting frames?

What is the version constant and why use it?

At the moment, the version is appended to relevant URLs to bust the response cached by Farcaster. It does it by appending a unique query param to the URL (classing web2 pattern). The value can be anything unique and I chose a timestamp for simplicity.

This is particularly useful if used to version releases of your Frame, manually or as part of a CI/CD pipeline.

Why not use the ImageResponse which uses satori under the hood?

That's just syntactic sugar.

What are all the possible <meta> tag properties relevant to building a Frame?

The minimum required properties are:

fc:frame (currently can only be vNext)



The minimum required properties are:

fc:frame (currently can only be vNext)



Other optional properties are:








Find the full spec with explanations in the official Farcaster docs.

See also

The state of the Open Graph protocol doesn't seem to be great. Twitter has cards, while Google went with another standard from

While the core value prop is simple: "encourage websites to include metadata which tells other platforms what they should look at", the value prop of similar standards might soon be challenged by AI, given the relatively narrow and content driven scope of the intention.

I'm hopeful this enhancement of the standard will live on at Farcaster and improvements like Frame Transactions take it to new heights. Building it in public, sufficiently-decentralised and outside the tutelage of one entity might lead to a different fate.

If you have further questions or need additional clarification on any of the content discussed in this tutorial, don't hesitate to reach out on Warpcast, Twitter, LinkedIn or GitHub. I'm always open for conversations and eager to assist!

Remember, learning is a journey, not a destination. Let's keep exploring together.

Thanks to everyone who reviewed this article, gave feedback and tips. Particularly Tommy and Mark.

Collect this post to permanently own it.
Tudor (two-door) 💡 Engineer turned fractional CTO logo
Subscribe to Tudor (two-door) 💡 Engineer turned fractional CTO and never miss a post.
  • Loading comments...