Cover photo

Not Your Average Frames Tutorial

A whistle-stop tour of building a Farcaser Frame

Tudor (two-door)

Tudor (two-door)

Intro

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.

Architecture

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

post image
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:

post image

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.

post image

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:

post image
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

post image
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:

post image

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

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

post image

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:

post image

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.

post image

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!

Play "MVP or Not MVP" here: https://warpcast.com/tudorizer/0xd122d681

Checkout the repo: https://github.com/tudormunteanu/frames-demo-1/

FAQ

Why did you not use frames.js, onchainkit or frog.fm?

Frames.js , frog.fm 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)

fc:frame:image

og:image

The minimum required properties are:

fc:frame (currently can only be vNext)

fc:frame:image

og:image

Other optional properties are:

fc:frame:button:$idx

fc:frame:post_url

fc:frame:button:$idx:action

fc:frame:button:$idx:target

fc:frame:input:text

fc:frame:image:aspect_ratio

fc:frame:state

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 schema.org.

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.

JAKEFarcaster
JAKE
Commented 4 months ago

Frame Review #4: Addresso by @tudorizer @addresso /addresso https://my.addresso.com

limone.eth 🍋Farcaster
limone.eth 🍋
Commented 4 months ago

i love this format, what are your favourite frames? i'm looking to get in touch with great frames builders!

tFarcaster
t
Commented 4 months ago

👋

limone.eth 🍋Farcaster
limone.eth 🍋
Commented 4 months ago

hello! can you share some frames you built?

tFarcaster
t
Commented 4 months ago

when you added a first entry through a frame, was this on mobile or on desktop?

JAKEFarcaster
JAKE
Commented 4 months ago

desktop

tFarcaster
t
Commented 4 months ago

thanks. I'm trying to replicate this, yet failing :(

Philip SheldrakeFarcaster
Philip Sheldrake
Commented 4 months ago

Thanks @jake for taking the time to both check out Addresso AND provide feedback 😍. We crave feedback as you might just guess 😀 and I'd love to pick up on a few of your observations. The thinking behind the button order is simply that no-one will sync if they're not returning, but someone with an existing Addresso Book might just click "Add" without first syncing, which would leave them with separate Addresso Books, which most definitely is not the idea! Will give this some more thought.

JAKEFarcaster
JAKE
Commented 4 months ago

perhaps "Create a new address book" and "I already have one" as simple 2 button options to start?

Philip SheldrakeFarcaster
Philip Sheldrake
Commented 4 months ago

Oooh nice. Saying that, we don't separate "Create a new address book" from "Add an entry". An address book is created WHEN the first entry is added. We think it's quite possible that no human being has ever thought "Oh yeah man, I need to create an address book" 😂 ... but questions such as "How and where can I store this address?" are necessity driven.

Philip SheldrakeFarcaster
Philip Sheldrake
Commented 4 months ago

We had "Contact" rather than "Entry" for a while pre-launch, but it felt very far from future proof. Or indeed, wholly relevant in the here and now. Addresso helps you store and organize blockchain addresses. That's all variety, not just those that relate to a contact, e.g. your own wallets, smart contracts, AI agents, ... We need words that already mean something for their communication power. And words that are also suited to this new technological paradigm. Sometimes it seems we spend as long thinking about the lexicon as writing the code!! What do you think?

JAKEFarcaster
JAKE
Commented 4 months ago

Maybe "address" then? Not a huge deal. I think "contact" is good because it's familiar and "entry" makes sense as future-proof and neutral, but you can always change it. Brand name should be stickier, though of course you can change that as well, but the reasons you decided to go for Addresso (I like the name) can probably be applied to support using "address" for the term

Philip SheldrakeFarcaster
Philip Sheldrake
Commented 4 months ago

Re. "Sign & Save", I think you worked this one out for yourself. Although it shouldn't confuse in the first place of course. On us. Signing is essential to give you the warm glow of knowing it's exactly what you think it is even though you've not used the address in question it in six months. Safe. Secure. But because it's a bit of effort, it makes sense to allow you to make many additions and/or edits before signing them all the once.

Insomnia 🎩Farcaster
Insomnia 🎩
Commented 1 year ago

I found this tutorial on how to create frames if you already know the basics of coding. https://paragraph.xyz/@tudorizer/not-your-average-frames-tutorial?referrer=0xbdca59f1346f6ccf05ee0c28ce4491cdf119fb4c

charli cohenFarcaster
charli cohen
Commented 1 year ago

this is great, thanks for sharing! 111 $degen

milan.⌐◨-◨ 🔵🐹🎩Farcaster
milan.⌐◨-◨ 🔵🐹🎩
Commented 1 year ago

this is great, ty for sharing 🙏 50 $DEGEN

Mel🎩🎭Farcaster
Mel🎩🎭
Commented 1 year ago

50 $degen

Insomnia 🎩Farcaster
Insomnia 🎩
Commented 1 year ago

Where could I find a tutorial on how to create frames for farcaster?

tFarcaster
t
Commented 1 year ago

If you’re already a dev, this might be useful: https://paragraph.xyz/@tudorizer/not-your-average-frames-tutorial

Ryan J. ShawFarcaster
Ryan J. Shaw
Commented 1 year ago

> send us a representative sample of your work

Shadi 📄 Farcaster
Shadi 📄
Commented 1 year ago

😂

nikolaii.eth 🚀Farcaster
nikolaii.eth 🚀
Commented 1 year ago

500 $degen 🍖x100

Ryan J. ShawFarcaster
Ryan J. Shaw
Commented 1 year ago

Did I get the job? 😄

dusan 🎩Farcaster
dusan 🎩
Commented 1 year ago

this is how it's done 👍 is it the fabled yonfrulizer?

Ryan J. ShawFarcaster
Ryan J. Shaw
Commented 1 year ago

Yup, I just need to figure out how to make a frame server now!

dusan 🎩Farcaster
dusan 🎩
Commented 1 year ago

if you needed help with that, let me know

Not Your Average Frames Tutorial