Building Farcaster Frames 101

Buidl On BaseBuidl On Base
6 min read

Farcaster, the SocialFi, offers opportunities for developers to deliver unique user experience, in the feeds of the users. This is done by building a frame to deliver your application

This tutorial is a walkthrough of this A Frame in 100 lines (or less). By the end, you'll have a working frame that you can test directly on Warpcast

What You'll Need

  • Vercel Account: To deploy your frame.

  • Basic Knowledge of Next.js: We'll use Next.js for our app.

  • OnchainKit: For handling frame interactions.

We already have OnchainKit configured in the Buidl On Base Starter repo. So to start we clone the repo

git clone https://github.com/projectbuidlonbase/buidl-on-base-starter

Open the cloned folder in your code editor. Delete all the files inside the web/src/app folder as we will not be using them for the frames. Instead create

  • config.ts

  • layout.tsx

  • page.tsx

Create a folder inside the src folder name it api. Inside this api folder you need to create another folder named frame. Create a file, route.ts inside the frame folder

Project Structure

Our folder structure for the app should look like this:

web
  ├──
  src
    ├──
    app/
      ├── config.ts
      ├── layout.tsx
      ├── page.tsx
    api/
      ├── frame/
          ├── route.ts

Open the src/app/config.ts file and add this code that defines the public url of the application

export const NEXT_PUBLIC_URL = 'https://zizzamia.xyz';

What we have done here is basically declaring and exporting a constant called NEXT_PUBLIC_URL, which is assigned a value. The export keyword makes this constant available to other modules in the project, so that we can import and use it anywhere we want to in our application.

The name NEXT_PUBLIC_URL is significant in Next.js applications, as the NEXT_PUBLIC_ prefix means that this environment variable will be exposed to the browser, making it available on both the server-side and client-side of the application.

Next open the src/app/layout.tsx file and add the following content

export const viewport = {
  width: 'device-width',
  initialScale: 1.0,
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

The viewport configuration is like setting up the perfect view for your app. You define the width and initial scale, so it looks great on any device - whether it's a mobile phone, tablet, or desktop. The width is set to match the device's width, and the initial scale is set to 1.0, which means no zooming, just the natural size of the content.

The RootLayout component is a React component that serves as the root, and it's exported as the default. It's simple, just an HTML structure with a body that includes the child components or elements passed to it. The "children" parameter is like a placeholder for whatever content you want to include.

Next open the src/app/page.tsx file. This is where we define the frame's metadata.

import { getFrameMetadata } from '@coinbase/onchainkit/frame';
import type { Metadata } from 'next';
import { NEXT_PUBLIC_URL } from './config';

const frameMetadata = getFrameMetadata({
  buttons: [
    {
      label: 'Story time!',
    },
    {
      action: 'link',
      label: 'Link to Google',
      target: 'https://www.google.com',
    },
    {
      label: 'Redirect to pictures',
      action: 'post_redirect',
    },
  ],
  image: {
    src: `${NEXT_PUBLIC_URL}/park-3.png`,
    aspectRatio: '1:1',
  },
  input: {
    text: 'Tell me a boat story',
  },
  postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
});

export const metadata: Metadata = {
  title: 'zizzamia.xyz',
  description: 'LFG',
  openGraph: {
    title: 'zizzamia.xyz',
    description: 'LFG',
    images: [`${NEXT_PUBLIC_URL}/park-1.png`],
  },
  other: {
    ...frameMetadata,
  },
};

export default function Page() {
  return (
    <>
      <h1>Welcome To My Frame</h1>
    </>
  );
}

First, we have some imports. We're bringing in a function called getFrameMetadata from @coinbase/onchainkit/frame, which helps generate metadata for a Farcaster frame. We're also importing a Metadata type from Next.js, which defines the shape of metadata that can be associated with a page. And we're importing a string constant called NEXT_PUBLIC_URL from our config.ts file, which represents is the base URL of the application.

Next, we're defining some frame metadata using the getFrameMetadata function. We're passing in a configuration object that defines the structure and behavior of a frame. This includes buttons, an image, input field, and a post URL. The buttons are interactive, the image is configured with a source URL and aspect ratio, the input field has placeholder text, and the post URL specifies where form data should be posted.

Now, let's talk about page metadata. We're exporting a metadata object that conforms to the Metadata type. This object defines the title and description of the page, as well as Open Graph metadata for better integration with social media platforms. We're also spreading the frameMetadata into the metadata object, so that the frame-specific metadata is included alongside the page metadata.

Finally, we have a simple page component. This is a functional React component that renders an h1 element with the text "Welcome To My Frame". This serves as the main visual element of the page, which can be expanded with additional content as needed.

We will now create a file that will handle the API requests. We will be using the src/api/frames/routes.ts file for this. It will handle the frame interactions using Onchainkit

import { FrameRequest, getFrameMessage, getFrameHtmlResponse } from '@coinbase/onchainkit/frame';
import { NextRequest, NextResponse } from 'next/server';
import { NEXT_PUBLIC_URL } from '../../config';

async function getResponse(req: NextRequest): Promise<NextResponse> {
  let accountAddress: string | undefined = '';
  let text: string | undefined = '';

  const body: FrameRequest = await req.json();
  const { isValid, message } = await getFrameMessage(body, { neynarApiKey: 'NEYNAR_ONCHAIN_KIT' });

  if (isValid) {
    accountAddress = message.interactor.verified_accounts[0];
  }

  if (message?.input) {
    text = message.input;
  }

  if (message?.button === 3) {
    return NextResponse.redirect(
      'https://www.google.com/search?q=cute+dog+pictures&tbm=isch&source=lnms',
      { status: 302 },
    );
  }

  return new NextResponse(
    getFrameHtmlResponse({
      buttons: [
        {
          label: `🌲 ${text} 🌲`,
        },
      ],
      image: {
        src: `${NEXT_PUBLIC_URL}/park-1.png`,
      },
      postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
    }),
  );
}

export async function POST(req: NextRequest): Promise<Response> {
  return getResponse(req);
}

export const dynamic = 'force-dynamic';

This part of the application, specifically this code, handles HTTP POST requests to process frame interactions and responds accordingly using the @coinbase/onchainkit/frame library.

First, we have some imports. We're bringing in types and functions from @coinbase/onchainkit/frame, Next.js API classes for handling server-side requests and responses, and a constant representing the base URL of the application.

Next, we have the getResponse function, which processes the frame request and generates an appropriate response. This async function does the following:

  • Parses the request body as JSON

  • Validates the request and extracts message details using getFrameMessage

  • Extracts the verified account address and input text from the message

  • Handles button actions, such as redirecting to a Google search for "cute dog pictures" if the third button is pressed

  • Generates an HTML response using getFrameHtmlResponse, including a button and image

Then, we have the POST handler, which is the exported handler for HTTP POST requests. It simply calls getResponse to process the request and return the response.

Finally, we have the dynamic page setting, which sets the page to be dynamically rendered. This forces the page to be re-rendered on each request, rather than serving a cached static version, which is useful when the page content changes frequently or depends on real-time data.

Push your code to github, then log into your vercel account to create a new project.Once your project is deployed on vercel you can test it here on warpcast

You should now have an idea of how to create and deploy a farcaster frame using the OnchainKit.

0
Subscribe to my newsletter

Read articles from Buidl On Base directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Buidl On Base
Buidl On Base