[object Object][object Object][object Object][object Object][object Object][object Object][object Object][object Object]Which Frames Framework to choose? | dTech Zum Inhalt springen

Which Frames Framework to choose?

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

This guide assumes you know what Farcaster Frames are.

There are three big TypeScript Farcaster Frame development frameworks that always come up in conversation.

Though which of them fits?

Choose your Frames Development Framework

One can clearly pick a fitting Framework according to their requirements. Mainly one needs to decide if integration into an existing code base, rendering as well as building frames or getting started quickly with very opinionated abstractions is the way to go.

  • Coinbase Onchainkit: types and helper functions as direct mapping of the Frames Specification, bring your own application
  • Frames.js: somewhere in the middle between abstractions and specification with a Frames Renderer for your Frontend built in
  • Frog.fm: heavily opinionated abstractions to remove boilerplate code aimed at web developers, renaming Frames Specification to their lingue

Coinbase Onchainkit

Now in case one prefers the official specification as their reference document the choice is Coinbase Onchainkit. No other Framework implements it directly while giving helper functions. The next closest being you writing the html meta tags. Onchainkit removes that hastle and simply produces them.

With the simple returns of responses or pure metadata that one can return or include in frontends, Onchainkit relies on the developer to do the application and backend building.

Coinbase Onchainkit has React components available, though personally I prefer to only use the core typescript functions and integrate into projects that way.

Frames.js

Frames.js started out by being the JSX of Frames. One defined their Frame in JSX syntax and that got included or returned as html metadata tags. Since v0.13.0 that changed to being an abstraction over the specification that aids in removing some of the code one needs to write every time like verifying the package sent by the Farcaster client.

The developer adds their own routes and then leverages the Frames.js abstractions to serve frames there. Routing between Frames is done by sending the user to a different route (routes handled by your Framework e.g. NextJS) or by sending to the same route as before and using State to derive what to return.

For example we want to send a user from /start to /next then we simply set the postUrl to /next and handle the next Frame there.

This API works in every TypeScript Framework while additionally having a Frame renderer component where one can build Frames into their own web applications.

With a Context object being available on a one time setup through so called Middlewares a developer can get enriched information within each route handler to work with. Say you add a middleware to add infromation about the user interacting with your Frame you could get their full profiles without needing to manually write the profile lookup.

Frames.js also brings a local debugger that can be started using npx frames

Frames.js Frames Debugger

Frog.fm

Frog is the most done for you Frames Development Framework out there. It registers routes, provides middleware adding context to your request data.

While it is quick to write Frames in Frog one will find themselves struggling to find information in the documentation. The documentation feels more like a reference where one opens each detailed reference document and sees if they can find what they know should be there as per Frames Specification.

Though you may not even find it by search 1:1 as Frog focuses on existing web developer naming conventions instead of adopting the naming conventions used in the Farcaster Specification and ecosystem.

One such example being the link action type where you want to show a button with a predefined link (e.g. dtech.vision). The Farcaster Frames specification calls this a link action with target dtech.vision while Frog makes you use the Link Subclass of Button with attribute href similar to html’s <a href=""></a>. Many more such examples exist which make it hard to find features coming from the official specification or other frameworks. The Frog documentation has no information on the renamings done and as mentioned may not enable you to find it.

If one is used to Frog though, the development speed is really useful for fast iterations and Proof of Concepts. The heavy use of abstractions helps, unless you end up doing complex frames and start fighting them.

Frog.fm also brings a local debugger that can be started using npx frog

Frog.fm Frames Debugger

Comparing code between Frameworks

While with Frames.js and Frog.fm you may end up fighting the abstractions in complex projects you are at times writing repetitive boiler plate in Coinbase Onchainkit.

Depending on the project choose your respective trade offs.

A simple ping then click a button and get pong frame in all three frameworks can be implemented in the following manner. The navigation from one Frame “Ping” to the next “Pong” is a good showcase of the basic layout and differences between the Frameworks.

State Management & Initial Setup

To start in Frames.js and Frog.fm we need to initially setup the abstractions. We do not need to do so in Coinbase Onchainkit. In case we were keeping track of state we would need to initialize that as well. For Onchainkit we can use the state in a Frame directly to pass serialized JSON objects.

It may just as well be helpful to define the State type with your types for use in Coinbase Onchainkit as well. Though you do not need any further initialization or the type.

frames.js
// file: src/app/frames/frames.ts
export type State = {
//JSON serializable state (no undefined :/)
// example:
favoriteColor: string;
age: number;
}
export const frames = createFrames<State>({
// ...
// define initial state if you want
initialState: {
favoriteColor: "green",
age: 10,
}
// ...
})
app/api/[[...routes]]/route.tsx
// frog.fm
type State = {
//JSON serializable state (no undefined :/)
// example:
favoriteColor: string;
age: number;
}
const app = new Frog<{State: State}>({
// ...
// define initial state if you want
initialState: {
favoriteColor: "green",
age: 10,
}
// ...
})

Note that Frog as of writing uses URL parameters while Onchainkit leaves it open how to pass state with the Frames Specifications State being the logical place. Frames.js comes with abstractions for both URL parameters and the State property. In addition Frames.js adds the possibility to define a signer key to achieve temper proof state due to it being signed by the backend.

Ping Pong Frame

Now we are ready for the “Ping”-Frame. Let’s do nothing else besides returning “Ping” and logging the user who pinged.

To make sure the user is actually the user they say they are, we require a signature (which is provided by default in signedMessageBytes sent to our Frame Server).

For verification we need to use hubs. For Frog and Frames.js this means either doing the call manually or using the middlewares to have it available in context. In Coinbase Onchainkit we will use a provided helper function.

app/api/ping/route.ts
// coinbase onchainkit
// ...
async function getResponse(req: NextRequest): Promise<NextResponse> {
const body: FrameRequest = await req.json();
const { isValid, message } = await getFrameMessage(body, { neynarApiKey: process.env.NEYNAR_API_KEY!! });
if(!isValid) {return new NextResponse('FrameMessage not valid!', { status: 500 }) }
console.log('Ping by', message.interactor.fid);
// you could do whatever for a new state and access current state with message.state
console.log('current State:', message.state);
const state: State = {
favoriteColor: "red",
age: 11,
}
return new NextResponse(
getFrameHTMLResponse({
state: state,
image: {
aspectRatio: '1:1',
src: 'https://dtech.vision/frame.png',
},
buttons: [
{
label: "Ping",
action: 'post', // post is the default action and can be omitted, explicitly written for the comparison
// NOTICE: this URL is the NEXT Frame or rather what we want to interact with. We want to send the Ping to let the user Pong too :D
target: `${NEXT_PUBLIC_URL}/api/pong`, // NEXT_PUBLIC_URL from app/config.ts (see Coinbase Onchainkit Frame Example)
}
]
})
);
}
export async function POST(req: NextRequest): Promise<Response> {
return getResponse(req);
// you could also do your logic directly here instead of calling getResponse, though that pattern allows for easy reuse of frames across the app
// by calling getResponse from another route (and you may even rename it to state which Frame it returns/handles)
}
frames.js
// file: app/frames/ping/route.tsx
// ...
export const POST = frames( async (ctx) => {
if(!ctx.message) { throw new Error('no message'); }
console.log('PING: current State:', ctx.state);
let state: State = {
favoriteColor: "red",
age: 11,
}
return {
state: state,
image: 'https://dtech.vision/frame.png',
imageOptions: {
aspectRatio: '1:1',
},
buttons: [
<Button action='post' target='/pong'>Ping</Button>
],
}
})
app/api/[[...routes]]/route.tsx
// frog.fm
// ...
app.frame('/ping', async (c) => {
const { deriveState } = c;
console.log('Ping by', c.frameData.fid);
if(c.frameData.fid == 3) {
// return an Error if fid 3 (Dan Romero) presses Ping
return c.error( { message: 'Hello Dan. Gjusta is closed today.' });
}
// you could do whatever for a new state and access current state with message.state
console.log('PING: current State:', c.previousState);
const state: State = deriveState( previousState => {
previousState.favoriteColor = "red";
previousState.age = 11;
}
);
return c.res({
image: 'https://dtech.vision/frame.png',
imageAspectRatio: '1:1',
intents: [
<Button action='/pong'> Pong </Button>,
],
});
})

As we can see the Ping Frames all point to pong if the user presses the Ping button. We need to also be able to handle the Pong route to return a frame there.

As a fun little gadget if Dan Romero (fid: 3) presses the Ping button we will return an error message. This error message will be shown as onscreen notification by the Farcaster clients. Dan sadly won’t be able to see our Pong frame, but gets a nice message. Of course these messages can be used for more, but it is a fun showcase.

Once someone presses pong the state will also change to be different which you’ll see by looking at your console output on the Frames Server.

app/api/pong/route.ts
// coinbase onchainkit
// ...
async function getResponse(req: NextRequest): Promise<NextResponse> {
const body: FrameRequest = await req.json();
const { isValid, message } = await getFrameMessage(body, { neynarApiKey: process.env.NEYNAR_API_KEY!! });
if(!isValid) {return new NextResponse('FrameMessage not valid!', { status: 500 }) }
console.log('Pong by', message.interactor.fid);
if(message.interactor.fid == 3) {
// return an Error if fid 3 (Dan Romero) presses Ping
return new NextResponse('Hello Dan. Gjusta is closed today.', { status: 500 });
}
// you could do whatever for a new state and access current state with message.state
console.log('PONG: current State:', message.state);
const state: State = {
favoriteColor: "blue",
age: 69,
}
return new NextResponse(
getFrameHTMLResponse({
state: state,
image: {
aspectRatio: '1:1',
src: 'https://dtech.vision/frame.png',
},
buttons: [
{
label: "Pong",
action: 'post', // post is the default action and can be omitted, explicitly written for the comparison
// NOTICE: this URL is the NEXT Frame or rather what we want to interact with. We want to get back to ping here
target: `${NEXT_PUBLIC_URL}/api/ping`, // NEXT_PUBLIC_URL from app/config.ts (see Coinbase Onchainkit Frame Example)
}
]
})
);
}
export async function POST(req: NextRequest): Promise<Response> {
return getResponse(req);
// you could also do your logic directly here instead of calling getResponse, though that pattern allows for easy reuse of frames across the app
// by calling getResponse from another route (and you may even rename it to state which Frame it returns/handles)
}
frames.js
// file: app/frames/pong/route.tsx
// ...
export const POST = frames( async (ctx) => {
if(!ctx.message) { throw new Error('no message'); }
if(ctx.message.requesterFid == 3) {
// return an Error if fid 3 (Dan Romero) presses Ping
throw new Error('Hello Dan. Gjusta is closed today.');
}
console.log('PING: current State:', ctx.state);
let state: State = {
favoriteColor: "blue",
age: 69,
}
return {
state: state,
image: 'https://dtech.vision/frame.png',
imageOptions: {
aspectRatio: '1:1',
},
buttons: [
<Button action='post' target='/pong'>Ping</Button>
],
}
})
app/api/[[...routes]]/route.tsx
// frog.fm
// ...
app.frame('/pong', async (c) => {
const { deriveState } = c;
console.log('Pong by', c.frameData.fid);
if(c.frameData.fid == 3) {
// return an Error if fid 3 (Dan Romero) presses Ping
return c.error( { message: 'Hello Dan. Gjusta is closed today.' });
}
// you could do whatever for a new state and access current state with message.state
console.log('PONG: current State:', c.previousState);
const state: State = deriveState( previousState => {
previousState.favoriteColor = "blue";
previousState.age = 69;
}
);
return c.res({
image: 'https://dtech.vision/frame.png',
imageAspectRatio: '1:1',
intents: [
<Button action='/ping'> Pong </Button>,
],
});
})
// ...

We have now built a sample Ping Pong Frame in all three frameworks. By looking at the code you can tell right away which suits your needs more. An mental model to know which to choose is available here.

To look at a more complex transaction frame with multiple steps, blockchain contract interaction, ccustom API calls and backend verifier logic to further check how each Framework behaves in complex settings refer to the following video.

Final remarks

Always remember Frames are HTML Metadata tags returned in the <Head> component of HTML responses. In case it gets to weird in a Farmework or setting one up seems to much, just use the HTML tags directly.

One can use HTML tags to point to the next Frames in a Framework. For example use HTML tags in Frontend to not add dependencies that point to a backend server running one of the Frames frameworks to handle further interactions.

In case you now want to develop a Frame head to the Quickstart Frames 101.