# Farcaster Mini Apps
## Getting Started
import { Caption } from '../../components/Caption';
### Overview
Mini apps are web apps built with HTML, CSS, and Javascript that can be discovered
and used within Farcaster clients. You can use an SDK to access native
Farcaster features, like authentication, sending notifications, and interacting
with the user's wallet.
### Quick Start
For new projects, you can set up an app using the
[@farcaster/create-mini-app](https://github.com/farcasterxyz/frames/tree/main/packages/create-mini-app)
CLI. This will prompt you to set up a project for your app.
:::code-group
```bash [npm]
npm create @farcaster/mini-app
```
```bash [pnpm]
pnpm create @farcaster/mini-app
```
```bash [yarn]
yarn create @farcaster/mini-app
```
:::
Remember, you can use whatever your favorite web framework is to build Mini
Apps so if these options aren't appealing you can setup the SDK in your own
project by following the instructions below.
### Manual Setup
For existing projects, install the Frames SDK:
#### Package Manager
:::code-group
```bash [npm]
npm install @farcaster/frame-sdk
```
```bash [pnpm]
pnpm add @farcaster/frame-sdk
```
```bash [yarn]
yarn add @farcaster/frame-sdk
```
:::
#### CDN
If you're not using a package manager, you can also use the Frame SDK via an
ESM-compatible CDN such as esm.sh. Simply add a `
```
### Building with AI
These docs are LLM friendly so that you use the latest models to build your
applications.
1. Use the Ask in ChatGPT buttons available on each page to interact with the
documentation.
2. Use the llms-full.txt to keep your LLM up to date with these docs:
Adding the Mini App docs to Cursor
#### How does this work?
This entire site is converted into a single markdown doc that can fit inside
the context window of most LLMs. See [The /llms.txt file](https://llmstxt.org/)
standards proposal for more information.
### Next Steps
You'll need to do a few more things before distributing your app to users:
1. publish the app by providing information about who created it and how it should displayed
2. make it sharable in feeds
## Specification
A Mini App is a web application that renders inside a Farcaster client.
### Mini App Embed
The primary discovery points for Mini Apps are social feeds. Mini App Embeds
are an OpenGraph-inspired metadata standard that lets any page in a Mini App
be rendered as a rich object that can launch user into an application.

#### Versioning
Mini App Embeds will follow a simple versioning scheme where non-breaking
changes can be added to the same version but a breaking change must accompany a
version bump.
#### Metatags
A Mini App URL must have a FrameEmbed in a serialized form in the `fc:frame` meta tag in the HTML ``. When this URL is rendered in a cast, the image is displayed in a 3:2 ratio with a button underneath. Clicking the button will open a Mini App to the provided action url and use the splash page to animate the transition.
```html
```
#### Schema
| Property | Type | Required | Description | Constraints |
| -------- | ------ | -------- | ----------------------- | ---------------------------------------------- |
| version | string | Yes | Version of the embed. | Must be "1" or "next" |
| imageUrl | string | Yes | Image url for the embed | Max 1024 characters. Must be 3:2 aspect ratio. |
| button | object | Yes | Button | |
##### Button Schema
| Property | Type | Required | Description | Constraints |
| -------- | ------ | -------- | -------------- | --------------------------- |
| title | string | Yes | Mini App name. | Max length 32 characters |
| action | object | Yes | Action | Max length 1024 characters. |
##### Action Schema
| Property | Type | Required | Description | Constraints |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- | -------------------------------------------- |
| type | string | Yes | Mini App name. | Max length 32 characters |
| url | string | No | App URL to open. If not provided, defaults to full URL used to fetch the document. | Max length 1024 characters. |
| name | string | No | | Name of the application |
| splashImageUrl | string | No | URL of image to show on loading screen. | Max length 32 characters. Must be 200x200px. |
| splashBackgroundColor | string | No | Hex color code to use on loading screen. | Hex color code. |
##### Example
```json
{
"version": "next",
"imageUrl": "https://yoink.party/framesV2/opengraph-image",
"button": {
"title": "🚩 Start",
"action": {
"type": "launch_frame",
"name": "Yoink!",
"url": "https://yoink.party/framesV2",
"splashImageUrl": "https://yoink.party/logo.png",
"splashBackgroundColor": "#f5f0ec"
}
}
}
```
### App Surface

#### Header
Hosts should render a header above the Mini App that includes the name and
author specified in the manifest. Clients should show the header whenever the
Mini App is launched.
#### Splash Screen
Hosts should show a splash screen as soon as the app is launched. The icon
and background must be specified in the Mini App manifest or embed meta tags.
The Mini App can hide the splash screen once loading is complete.

#### Size & Orientation
A Mini App should be rendered in a vertical modal. Mobile Mini App sizes should
be dictated by device dimensions while web Mini App sizes should be set to
424x695px.
### SDK
Mini Apps can communicate with their Host using a JavaScript SDK. At this time
there is no formal specification for the message passing format, Hosts and Apps
should use the open-source NPM packages that can be found in the
[farcasterxyz/miniapps](https://github.com/farcasterxyz/miniapps) repo.
This SDK facilitates communication over a `postMessage` channel available in
iframes and mobile WebViews.
#### Versioning
The SDK is versioned using [Semantic Versioning](https://semver.org/). A
[What's New page](/docs/sdk/changelog) is maintained to communicate developer
impacting changes. A [lower level
changelog](https://github.com/farcasterxyz/miniapps/blob/main/packages/frame-sdk/CHANGELOG.md)
is maintained within the code base to document all changes.
#### API
* [context](/docs/sdk/context) - provides information about the context the Mini App is running in
##### Actions
* [addFrame](/docs/sdk/actions/add-frame) - Prompts the user to add the Mini App
* [close](/docs/sdk/actions/close) - Closes the Mini App
* [composeCast](/docs/sdk/actions/compose-cast) - Prompt the user to cast
* [ready](/docs/sdk/actions/ready) - Hides the Splash Screen
* [signin](/docs/sdk/actions/sign-in) - Prompts the user to Sign In with Farcaster
* [openUrl](/docs/sdk/actions/open-url) - Open an external URL
* [viewProfile](/docs/sdk/actions/view-profile) - View a Farcaster profile
##### Wallet
* [ethProvider](/docs/sdk/wallet) - [EIP-1193 Ethereum Provider](https://eips.ethereum.org/EIPS/eip-1193)
#### Events
The SDK allows Mini Apps to [subscribe to events](/docs/sdk/events) emitted by the Host.
### Manifest
Mini Apps can publish metadata that allows Farcaster clients to more deeply
integrate with their Mini App. This file is published at
`/.well-known/farcaster.json` and the [Fully Qualified Domain
Name](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) where it is
hosted uniquely identifies the Mini App. The Manifest contains data that allows
Farcaster clients to verify the author of the app, present the Mini App in
discovery surfaces like app stores, and allows the Mini App to send
notifications.
#### Versioning
Manfiests will follow a simple versioning scheme where non-breaking
changes can be added to the same version but a breaking change must accompany a
version bump.
#### Schema
| Property | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------------------ |
| accountAssociation | object | Yes | Verifies domain ownership to a Farcaster account |
| frame | object | Yes | Metadata about the Mini App |
##### accountAssociation
The account association verifies authorship of this domain to a Farcaster
account.
The value is set to the JSON representation of a [JSON Farcaster
Signature](https://github.com/farcasterxyz/protocol/discussions/208) from the
account's custody address with the following payload:
```json
{
domain: string;
}
```
The `domain` value must exactly match the FQDN of where it is hosted.
##### Schema
| Property | Type | Required | Description |
| --------- | ------ | -------- | ------------------------- |
| header | string | Yes | base64 encoded JFS header |
| payload | string | Yes | base64 encoded payload |
| signature | string | Yes | base64 encoded signature |
##### Example
```json
{
"header": "eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0",
"payload": "eyJkb21haW4iOiJ5b2luay5wYXJ0eSJ9",
"signature": "MHgwZmJiYWIwODg3YTU2MDFiNDU3MzVkOTQ5MDRjM2Y1NGUxMzVhZTQxOGEzMWQ5ODNhODAzZmZlYWNlZWMyZDYzNWY4ZTFjYWU4M2NhNTAwOTMzM2FmMTc1NDlmMDY2YTVlOWUwNTljNmZiNDUxMzg0Njk1NzBhODNiNjcyZWJjZTFi"
}
```
##### frame
Metadata needed to by Hosts to distribute the Mini App.
import ManifestAppConfigSchema from "../../snippets/manifestAppConfigSchema.mdx"
##### Example
import ManifestAppConfigExample from "../../snippets/manifestAppConfigExample.mdx"
#### Example
Example of a valid farcaster.json manifest:
```json
{
"accountAssociation": {
"header": "eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0",
"payload": "eyJkb21haW4iOiJ5b2luay5wYXJ0eSJ9",
"signature": "MHgwZmJiYWIwODg3YTU2MDFiNDU3MzVkOTQ5MDRjM2Y1NGUxMzVhZTQxOGEzMWQ5ODNhODAzZmZlYWNlZWMyZDYzNWY4ZTFjYWU4M2NhNTAwOTMzM2FmMTc1NDlmMDY2YTVlOWUwNTljNmZiNDUxMzg0Njk1NzBhODNiNjcyZWJjZTFi"
},
"frame": {
"version": "1",
"name": "Yoink!",
"iconUrl": "https://yoink.party/logo.png",
"homeUrl": "https://yoink.party/framesV2/",
"imageUrl": "https://yoink.party/framesV2/opengraph-image",
"buttonTitle": "🚩 Start",
"splashImageUrl": "https://yoink.party/logo.png",
"splashBackgroundColor": "#f5f0ec",
"webhookUrl": "https://yoink.party/api/webhook"
}
}
```
#### Caching
Farcaster clients may cache the manifest for a Mini App but should provide a
way for refreshing the manifest file.
### Adding Mini Apps
Mini Apps can be added to their Farcaster client by users. This enables the user
to quickly navigate back to the app and the app to send notifications to the
user.
Mini Apps can prompt the user to add the app during an interaction with the
[addFrame](/docs/sdk/actions/add-frame) action. Hosts may also let users add Mini
Apps from discovery surfaces like app stores or featured notifications.
Before a user adds a Mini App the Host should display information about the app
and a reminder that the app will be able to notify the user.
When a user adds a Mini App the Host must generate the appropriate Server
Events and send them to the Mini App's `webhookUrl` if one was provided.
After a user adds a Mini App, the Host should make it easy to find and launch
the Mini App by providing a top-level interface where users can browse and open
added apps.
#### Server Events
The Host server POSTs 4 types of events to the Mini App server at the
`webhookUrl` specified in its Mini App manifest:
* `frame_added`
* `frame_removed`
* `notifications_enabled`
* `notifications_disabled`
The body looks like this:
Events use the [JSON Farcaster
Signature](https://github.com/farcasterxyz/protocol/discussions/208) format and
are signed with the app key of the user. The final format is:
```
{
header: string;
payload: string;
signature: string;
}
```
All 3 values are `base64url` encoded. The payload and header can be decoded to
JSON, where the payload is different per event.
##### frame\_added
This event may happen when an open frame calls `actions.addFrame` to prompt the
user to favorite it, or when the frame is closed and the user adds the frame
elsewhere in the client application (e.g. from a catalog).
Adding a frame includes enabling notifications.
The Host server generates a unique `notificationToken` and sends it
together with the `notificationUrl` that the frame must call, to both the
Host client and the frame server. Client apps must generate unique
tokens for each user.
Webhook payload:
```json
{
"event": "frame-added",
"notificationDetails": {
"url": "https://api.warpcast.com/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
```ts
type EventFrameAddedPayload = {
event: 'frame_added';
notificationDetails?: FrameNotificationDetails;
};
```
##### frame\_removed
A user can remove a frame, which means that any notification tokens for that
fid and client app (based on signer requester) should be considered invalid:
Webhook payload:
```json
{
"event": "frame-removed"
}
```
##### notifications\_disabled
A user can disable frame notifications from e.g. a settings panel in the client
app. Any notification tokens for that fid and client app (based on signer
requester) should be considered invalid:
Webhook payload:
```json
{
"event": "notifications_disabled"
}
```
##### notifications\_enabled
A user can enable frame notifications (e.g. after disabling them). The client
backend again sends a `notificationUrl` and a `token`, with a backend-only
flow:
Webhook payload:
```json
{
"event": "notifications-enabled",
"notificationDetails": {
"url": "https://api.warpcast.com/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
```ts
type EventNotificationsEnabledPayload = {
event: 'notifications_enabled';
notificationDetails: FrameNotificationDetails;
};
```
#### Notifications
A Mini App server can send notifications to one or more users who have enabled
them.
The Mini App server is given an authentication token and a URL which they can
use to push a notification to the specific Farcaster app that invoked the Mini
App. This is private and must be done separately for each Farcaster client that
a user may use.
The Mini App server calls the `notificationUrl` with the following JSON body:
import SendNotificationRequestSchema from '../../snippets/sendNotificationRequestSchema.mdx'
The response from the client server must be an HTTP 200 OK with the following JSON body:
import SendNotificationResponseSchema from '../../snippets/sendNotificationResponseSchema.mdx'
Once a user has been notified, when clicking the notification the client app will:
* Open `targetUrl`
* Set the context to the notification, see `NotificationLaunchContext`
##### Rate Limits
Host servers should impose rate limits per `token` to prevent intentional or accidentally abuse. The recommended rate limits are:
* 1 notification per 30 seconds per `token`
* 100 notifications per day per `token`
##### Displaying notifications
Hosts should display a user's Mini App notifications from their UI as follows:

##### Controls
Hosts should provide controls for the user to toggle their notification
settings for their apps.
* Users should be able to navigate to settings for any Mini App they've added
and be able to enable or disable notifications from this menu
* Users should be able to disable notifications for a Mini App directly from a
notification from that Mini App
import { Caption } from '../../../components/Caption';
## Authenticating users
An app can use the [signIn](/docs/sdk/actions/sign-in) to get a [Sign in with
Farcaster (SIWF)](https://docs.farcaster.xyz/developers/siwf/) authentication credential for the user.
After requesting the credential, applications can verify it on their server
using
[verifySignInMessage](https://docs.farcaster.xyz/auth-kit/client/app/verify-sign-in-message).
Apps can then issue a session token like a JWT that can be used for the
remainder of the session.
Session tokens should be kept in memory but not persisted in Local Storage or
Cookies. Since users are signing in through their Farcaster client their
expectation will be if they sign out of the their Farcaster client they'll be
signed out of any Mini Apps as well.
### User Experience
In cases where the Farcaster client (i.e. on mobile) has direct access to the
user's signing key (e.g. their custody account) this credential can be produced
silently without the user needing to take any action. Otherwise the user will be
prompted to sign in.
Farcaster clients are working to support silent sign-in across all platforms so
that users are never prompted to sign in on a different device.

A users opens an app and is automatically signed in
import { Caption } from '../../../components/Caption';
## Loading your app
When users open Mini Apps in Farcaster they are shown a branded splash screen
instead of a blank loading page like they would in a browser. Once your interface
is ready to show the splash screen can be hidden.

Dismiss the Splash Screen with ready.
### Calling ready
Call [ready](/docs/sdk/actions/ready) when your interface is ready to be displayed:
```ts twoslash
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.ready();
```
**You should call ready as soon as possible while avoiding jitter and content
reflows.**
Minimize loading time for your app by following web performance best practices:
* [Learn about web performance](https://web.dev/learn/performance)
* [Test your app's speed and diagnose performance issues](https://pagespeed.web.dev/analysis/https-pagespeed-web-dev/bywca5kqd1?form_factor=mobile)
To avoid jitter and content reflowing:
* Don't call ready until your interface has loaded
* Use placeholders and skeleton states if additional loading is required
#### Disabling native gestures
Mini Apps are rendered in modal elements where certain swipe gestures or clicks
outside the app surface will result in the app closing. If your app has conflicting
gestures you can set the `disableNativeGestures` flag to disable native gestures.
```ts
await sdk.actions.ready({ disableNativeGestures: true });
```
### Splash Screen
When a user launches your app they will see a Splash Screen while your app loads.

You'll learn how to configure the Splash Screen in the [sharing your
app](/docs/guides/sharing) and [publishing your app](/docs/guides/publishing)
guides.
### Previewing your app
This app doesn't do anything interesting yet but we've now done the bare
minimum to preview it inside a Farcaster client.
Let's preview it in Warpcast:
1. Open the [Mini App Debug Tool](https://warpcast.com/~/developers/mini-apps/debug)
on desktop
2. Enter your app url
3. Hit *Preview*
import { Caption } from '../../../components/Caption';
## Sending Notifications
Mini Apps can send notifications to users who have added the Mini App to
their Farcaster client and enabled notifications.

An in-app notification is sent to a user and launches them into the app
### Overview
At a high-level notifications work like so:
* when a user enables notifications for your app, the Farcaster client (i.e. Warpcast)
hosting your app will generate a notification token for that user and send it
to your server
* to send a notification to a user, make a request to host's servers with the
notification token and content
* if a user later disables notifications, you'll receive another event indicating
the user is unsubscribed and the notification token is no longer valid
### Steps
::::steps
#### Listen for events
You'll need a server to receive webhook events and a database to store
notification tokens for users:
* **Managed** - If you'd rather stay focused on your app, use
[Neynar](https://neynar.com) to manage notification tokens on your behalf:
[Setup a managed notifications server with
Neynar](https://docs.neynar.com/docs/send-notifications-to-frame-users#step-1-add-events-webhook-url-to-frame-manifest).
* **Roll your own** - If you want to host your own server to receive webhooks:
[Follow the Receiving Webhooks guide](#receiving-webhooks).
#### Add your webhook URL in `farcaster.json`
If you haven't already, follow the [Publishing your app](/docs/guides/publishing) guide to host a
`farcaster.json` on your app's domain.
Define the `webhookUrl` property in your app's configuration in `farcaster.json`:
```json
{
"accountAssociation": {
"header": "eyJmaWQiOjU0NDgsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHg2MWQwMEFENzYwNjhGOEQ0NzQwYzM1OEM4QzAzYUFFYjUxMGI1OTBEIn0",
"payload": "eyJkb21haW4iOiJleGFtcGxlLmNvbSJ9",
"signature": "MHg3NmRkOWVlMjE4OGEyMjliNzExZjUzOTkxYTc1NmEzMGZjNTA3NmE5OTU5OWJmOWFmYjYyMzAyZWQxMWQ2MWFmNTExYzlhYWVjNjQ3OWMzODcyMTI5MzA2YmJhYjdhMTE0MmRhMjA4MmNjNTM5MTJiY2MyMDRhMWFjZTY2NjE5OTFj"
},
"frame": {
"version": "1",
"name": "Example App",
"iconUrl": "https://example.com/icon.png",
"homeUrl": "https://example.com",
"imageUrl": "https://example.com/image.png",
"buttonTitle": "Check this out",
"splashImageUrl": "https://example.com/splash.png",
"splashBackgroundColor": "#eeccff",
"webhookUrl": "https://example.com/api/webhook" // [!code focus]
}
}
```
:::note
For a real example, this is Yoink's manifest:
[https://yoink.party/.well-known/farcaster.json](https://yoink.party/.well-known/farcaster.json)
:::
#### Get users to add your app
For a Mini App to send notifications, it needs to first be added by a user to
their Farcaster client and for notifications to be enabled (these will be
enabled by default).
Use the [addFrame](/docs/sdk/actions/add-frame) action while a user is using your app to prompt
them to add it:
#### Save the notification tokens
When notifications are enabled, the Farcaster client generates a unique
notification token for the user. This token is sent webhook endpoint along with
a `url` that the app should call to send a notification.
The `token` and `url` need to be securely saved to database so they can be
looked up when you want to send a notification to a particular user.
#### Send a notification

Once you have a notification token for a user, you can send them a notification
by sending a `POST` request the `url` associated with that token.
:::tip
If your are sending the same notification to multiple users, you batch up to a
100 sends in a single request by providing multiple `tokens`.
:::
The body of that request must match the following JSON schema:
import SendNotificationRequestSchema from '../../../snippets/sendNotificationRequestSchema.mdx'
The server should response with an HTTP 200 OK and the following JSON body:
import SendNotificationResponseSchema from '../../../snippets/sendNotificationResponseSchema.mdx'
When a user clicks the notification, the Farcaster client will:
* Open your Mini App at `targetUrl`
* Set the `context.location` to a `FrameLocationNotificationContext`
```ts
export type FrameLocationNotificationContext = {
type: 'notification';
notification: {
notificationId: string;
title: string;
body: string;
};
};
```
[Example code to send a
notification](https://github.com/farcasterxyz/frames-v2-demo/blob/7905a24b7cd254a77a7e1a541288379b444bc23e/src/app/api/send-notification/route.ts#L25-L65)
##### Rate Limits
Host servers may impose rate limits per `token`. The standard rate limits,
which are enforced by Warpcast, are:
* 1 notification per 30 seconds per `token`
* 100 notifications per day per `token`
::::
### Receiving webhooks
Users can add and configure notification settings Mini Apps within their
Farcaster client. When this happens Farcaster clients will send events your
server that include data relevant to the event.
This allows your app to:
* keep track of what users have added or removed your app
* securely receive tokens that can be used to send notifications to your users
:::note
If you'd rather stay focused on your app, [Neynar](https://neynar.com) offers a
[managed service to handle
webhooks](https://docs.neynar.com/docs/send-notifications-to-frame-users#step-1-add-events-webhook-url-to-frame-manifest)
on behalf of your application.
:::
#### Events
##### frame\_added
Sent when the user adds the Mini App to their Farcaster client (whether or not
this was triggered by an `addFrame()` prompt).
The optional `notificationDetails` object provides the `token` and `url` if the
client equates adding to enabling notifications (Warpcast does this).
##### Payload
```json
{
"event": "frame_added",
"notificationDetails": {
"url": "https://api.warpcast.com/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
##### frame\_removed
Sent when a user removes a mini app, which means that any notification tokens for
that fid and client app (based on signer requester) should be considered
invalid:
##### Payload
```json
{
"event": "frame_removed"
}
```
##### notifications\_disabled
Sent when a user disables notifications from e.g. a settings panel in the
client app. Any notification tokens for that fid and client app (based on
signer requester) should be considered invalid:
##### Payload
```json
{
"event": "notifications_disabled"
}
```
##### notifications\_enabled
Sent when a user enables notifications (e.g. after disabling them). The payload
includes a new `token` and `url`:
##### Payload
```json
{
"event": "notifications_enabled",
"notificationDetails": {
"url": "https://api.warpcast.com/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
#### Handling events
Farcaster clients will POST events to the `webhookUrl` specified in your `farcaster.json`.
Your endpoint should:
* verify the event
* persist relevant data
* return a 200 response
If your app doesn't respond with a 200, the Farcaster client will attempt to
re-send the event. The exact number of retries is up to each client.
#### Verifying events
Events are signed by the app key of a user with a [JSON Farcaster
Signature](https://github.com/farcasterxyz/protocol/discussions/208). This allows
Mini Apps to verify the Farcaster client that generated the notification and the
Farcaster user they generated it for.
The
[`@farcaster/frame-node`](https://github.com/farcasterxyz/frames/tree/main/packages/frame-node)
library provides a helper for verifying events. To use it, you'll need to supply a validation
function that can check the signatures against the latest Farcaster network state.
An implementation that uses [Neynar](https://neynar.com) is provided. You can sign up and get
an API key on their free tier. Make sure to set `NEYNAR_API_KEY` environment variable.
#### Example
```ts twoslash
const requestJson = "base64encodeddata";
// ---cut---
import {
ParseWebhookEvent,
parseWebhookEvent,
verifyAppKeyWithNeynar,
} from "@farcaster/frame-node";
try {
const data = await parseWebhookEvent(requestJson, verifyAppKeyWithNeynar);
} catch (e: unknown) {
const error = e as ParseWebhookEvent.ErrorType;
switch (error.name) {
case "VerifyJsonFarcasterSignature.InvalidDataError":
case "VerifyJsonFarcasterSignature.InvalidEventDataError":
// The request data is invalid
case "VerifyJsonFarcasterSignature.InvalidAppKeyError":
// The app key is invalid
case "VerifyJsonFarcasterSignature.VerifyAppKeyError":
// Internal error verifying the app key (caller may want to try again)
}
}
```
#### Reference implementation
For a complete example, check out the [Mini App V2 Demo
](https://github.com/farcasterxyz/frames-v2-demo) has all of the above:
* [Handles webhooks](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/app/api/webhook/route.ts) leveraging the [`@farcaster/frame-node`](https://github.com/farcasterxyz/frames/tree/main/packages/frame-node) library that makes this very easy
* [Saves notification tokens to Redis](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/lib/kv.ts)
* [Sends notifications](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/lib/notifs.ts)
import { Caption } from '../../../components/Caption';
## Publishing your app
Publishing Mini Apps involves providing information like who developed the app,
how it should be displayed, and what its capabilities are.
Since Farcaster is a decentralized network with multiple clients, publishing is
done by hosting a manifest file at `/.well-known/farcaster.json` on the domain
your app is hosted on rather than submitting information directly to a single
entity.

Published Mini Apps can be discovered in App Stores.
### Steps
::::steps
#### Choose a domain
A Mini App is associated with a single domain (i.e. rewards.warpcast.com). This
domain serves as the identifier for your app and can't be changed later so
you should choose a stable domain.
There's no limit on the number of apps you can create. You can create a separate
domain specifically for development purposes if needed.
:::note
A domain does not include the scheme (e.g. https) or path. It can optionally
include a subdomain.
* ✅ rewards.warpcast.com
* ❌ [https://rewards.warpcast.com](https://rewards.warpcast.com)
:::
#### Host a manifest file
Host a manifest file on your chosen domain at `/.well-known/farcaster.json`.
For now we'll create an empty file:
```sh
touch public/.well-known/farcaster.json
```
#### Define your application configuration
A Mini App has metadata that is used by Farcaster clients to host your app. This
data is specified in the `frame` property of the manifest and has the following properties:
import ManifestAppConfigSchema from "../../../snippets/manifestAppConfigSchema.mdx"
Here's an example `farcaster.json` file:
```json
{
"frame": {
"version": "1",
"name": "Yoink!",
"iconUrl": "https://yoink.party/logo.png",
"homeUrl": "https://yoink.party/framesV2/",
"imageUrl": "https://yoink.party/framesV2/opengraph-image",
"buttonTitle": "🚩 Start",
"splashImageUrl": "https://yoink.party/logo.png",
"splashBackgroundColor": "#f5f0ec",
}
}
```
:::note
You can omit `webhookUrl` for now. We'll show you how to set it up in the
[sending notifications guide](/docs/guides/notifications).
:::
### Verifying ownership
A Mini App is owned by a single Farcaster account. This lets users know who
they are interacting with and developers get credit for their work.
:::tip
Verified Mini Apps are automatically eligible for [Warpcast Developer
Rewards](https://warpcast.com/~/mini-apps/rewards) that are paid out weekly
based on usage and onchain transactions.
:::

Verification is done by placing a cryptographically signed message in the
`accountAssociation` property of your `farcaster.json`.
You can generate a signed account association object using the [Mini App
Manifest Tool](https://warpcast.com/~/developers/new) in Warpcast. Take
the output from that tool and update your `farcaster.json` file.
:::warning
The domain you host the file on must exactly match the domain you entered in
the Warpcast tool.
:::
Here's an example `farcaster.json` file for the domain `yoink.party` with the
account association:
```json
{
"accountAssociation": {
"header": "eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0",
"payload": "eyJkb21haW4iOiJ5b2luay5wYXJ0eSJ9",
"signature": "MHgwZmJiYWIwODg3YTU2MDFiNDU3MzVkOTQ5MDRjM2Y1NGUxMzVhZTQxOGEzMWQ5ODNhODAzZmZlYWNlZWMyZDYzNWY4ZTFjYWU4M2NhNTAwOTMzM2FmMTc1NDlmMDY2YTVlOWUwNTljNmZiNDUxMzg0Njk1NzBhODNiNjcyZWJjZTFi"
},
"frame": {
"version": "1",
"name": "Yoink!",
"iconUrl": "https://yoink.party/logo.png",
"homeUrl": "https://yoink.party/framesV2/",
"imageUrl": "https://yoink.party/framesV2/opengraph-image",
"buttonTitle": "🚩 Start",
"splashImageUrl": "https://yoink.party/logo.png",
"splashBackgroundColor": "#f5f0ec",
"webhookUrl": "https://yoink.party/api/webhook"
}
}
```
::::
import { Caption } from '../../../components/Caption';
## Sharing your app
Mini Apps can be shared in social feeds using special embeds that let users
interact with an app directly from their feed.
Each URL in your application can be made embeddable by adding meta tags to it
that specify an image and action, similar to how Open Graph tags work.
For example:
* a personality quiz app can let users share a personalized embed with their results
* an NFT marketplace can let users share an embed for each listing
* a prediction market app can let users share an embed for each market

A viral loop: user discovers app in feed → uses app → shares app in feed
### Sharing a page in your app
Add a meta tag in the `` section of the page you want to make
sharable specifying the embed metadata:
```html
```
When a user shares the URL with your embed on it in a Farcaster client, the
Farcaster client will fetch the HTML, see the `fc:frame` meta tag, and use it
to render a rich card.
### Properties

#### `version`
The string literal `'next'`.
#### `imageUrl`
The URL of the image that should be displayed.
* the image will be displayed at 3:2 aspect ratio.
* the image must be less than 10MB
* The URL must be \<= 1024 characters
#### `button.title`
This text will be rendered in the button. Use a clear call-to-action that hints
to the user what action they can take in your app.
#### `button.action.type`
The string literal `'launch_frame'`.
#### `button.action.url` (optional)
The URL that the user will be sent to within your app. If not provided, it defaults to the current webpage URL (including query parameters).
#### `button.action.name` (optional)
Name of the application. Defaults to name of your application in `farcaster.json`.
#### `button.action.splashImageUrl` (optional)
Splash image URL. Defaults to `splashImageUrl` specified in your application's `farcaster.json`.
#### `button.action.splashBackgroundColor` (optional)
Splash image Color. Defaults to `splashBackgroundColor` specified in your application's `farcaster.json`.
### Example
```typescript
const frame = {
version: "next",
imageUrl: "https://yoink.party/framesV2/opengraph-image",
button: {
title: "🚩 Start",
action: {
type: "launch_frame",
url: "https://yoink.party/framesV2",
name:"Yoink!",
splashImageUrl: "https://yoink.party/logo.png",
splashBackgroundColor:"#f5f0ec"
}
}
}
```
```html
```
### Debugging
You can use the [Mini App Embed
Tool](https://warpcast.com/~/developers/mini-apps/embed) in Warpcast to preview
a embed.
import ExposeLocalhost from '../../../snippets/exposeLocalhost.mdx';
### Caching
Since embeds are shared in feeds, they are generally scraped once and cached so
that they can be efficiently served in the feeds of hundreds or thousands
users.
This means that when a URL gets shared, the embed data present at that time
will be attached to the cast and won't be updated even if the embed data at
that URL gets changed.
#### Lifecycle
1. App adds an `fc:frame` meta tag to a page to make it sharable.
2. User copies URL and embeds it in a cast.
3. Farcaster client fetches the URL and attaches the frame metadata to the cast.
4. Farcaster client injects the cast + embed + attached metadata into thousands of feeds.
5. User sees cast in feed with an embed rendered from the attached metadata.
### Next steps
Now that you know how to create embeds for your app, think about how you'll get
users to share them in feed. For instance, you can create a call-to-action once
a user takes an action in your app to share a embed in a cast.
At the very least you'll want to setup a embed for the root URL of your application.
### Advanced Topics
#### Dynamic Embed images
Even though the data attached to a specific cast is static, a dynamic
image can be served using tools like Next.js
[Next ImageResponse](https://nextjs.org/docs/app/api-reference/functions/image-response).
For example, we could create an embed that shows the current price of ETH. We'd
set the `imageUrl` to a static URL like `https://example.xyz/eth-price.png`. When a request
is made to this endpoint we'd:
* fetch the latest price of ETH (ideally from a cache)
* renders an image using a tool like [Vercel
OG](https://vercel.com/docs/functions/og-image-generation) and returns it
* sets the following header: `Cache-Control: public, immutable, no-transform,
max-age=300`
##### Setting `max-age`
You should always set a non-zero `max-age` (outside of testing) so that the
image can get cached and served from CDNs, otherwise users will see a gray
image in their feed while the dynamic image is generated. You'll also quickly
rack up a huge bill from your servie provider. The exact time depends on your
application but opt for the longest time that still keeps the image reasonably
fresh. If you're needing freshness less than a minute you should reconsider
your design or be prepared to operate a high-performance endpoint.
Here's some more reading if you're interested in doing this:
* [Vercel Blog - Fast, dynamic social card images at the Edge](https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images)
* [Vercel Docs - OG Image Generation](https://vercel.com/docs/og-image-generation)
##### Avoid caching fallback images
If you are generating a dynamic images there's a chance something goes wrong when
generating the image (for instance, the price of ETH is not available) and you need
to serve a fallback image.
In this case you should use an extremely short or even 0 `max-age` to prevent the
error image from getting stuck in any upstream CDNs.
import { Caption } from '../../../components/Caption';
## Interacting with Wallets
Mini Apps can interact with a user's crypto wallet without needing to worry
about popping open "select your wallet" dialogs or flakey connections.

A user minting an NFT using the Warpcast Wallet.
### Getting Started
The Mini App SDK exposes an [EIP-1193 Ethereum Provider
API](https://eips.ethereum.org/EIPS/eip-1193) at `sdk.wallet.ethProvider`.
We recommend using [Wagmi](https://wagmi.sh) to connect to and interact with
the users wallet. This is not required but provides high-level hooks for
interacting with the wallet in a type-safe way.
::::steps
#### Setup Wagmi
Use the [Getting Started
guide](https://wagmi.sh/react/getting-started#manual-installation) to setup
Wagmi in your project.
#### Install the connector
Next we'll install a Wagmi connector that will be used to interact with the
user's wallet:
:::code-group
```bash [npm]
npm install @farcaster/frame-wagmi-connector
```
```bash [pnpm]
pnpm add @farcaster/frame-wagmi-connector
```
```bash [yarn]
yarn add @farcaster/frame-wagmi-connector
```
:::
#### Add to Wagmi configuration
Add the Mini App connector to your Wagmi config:
```ts
import { http, createConfig } from 'wagmi'
import { base } from 'wagmi/chains'
import { farcasterFrame as miniAppConnector } from '@farcaster/frame-wagmi-connector'
export const config = createConfig({
chains: [base],
transports: {
[base.id]: http(),
},
connectors: [
miniAppConnector()
]
})
```
#### Connect to the wallet
If a user already has a connected wallet the connector will automatically
connect to it (e.g. `isConnected` will be true).
It's possible a user doesn't have a connected wallet so you should always check
for a connection and prompt them to connect if they aren't already connected:
```tsx
import { useAccount, useConnect } from 'wagmi'
function ConnectMenu() {
const { isConnected, address } = useAccount()
const { connect, connectors } = useConnect()
if (isConnected) {
return (
<>
You're connected!
Address: {address}
>
)
}
return (
)
}
```
:::note
Your Mini App won't need to show a wallet selection dialog that is common in a
web based dapp, the Farcaster client hosting your app will take care of getting
the user connected to their preferred crypto wallet.
:::
#### Send a transaction
You're now ready to prompt the user to transact. They will be shown a preview
of the transaction in their wallet and asked to confirm it:
Follow [this guide from
Wagmi](https://wagmi.sh/react/guides/send-transaction#_2-create-a-new-component)
on sending a transaction (note: skip step 1 since you're already connected to
the user's wallet).
::::
### Troubleshooting
#### Transaction Scanning
Modern crypto wallets scan transactions and preview them to users to help
protect users from scams. New contracts and applications can generate false
positives in these systems. If your transaction is being reported as
potentially malicious use this [Blockaid
Tool](https://report.blockaid.io/verifiedProject) to verify your app with
Blockaid.
## What's New
### April 22, 2025 (0.0.36)
* Added `noindex` field to manifest (see [discussions/204](https://github.com/farcasterxyz/miniapps/discussions/204))
### April 16, 2025 (0.0.35)
* Introduced new manifest metadata fields (see [discussions/191](https://github.com/farcasterxyz/miniapps/discussions/191))
* Deprecated `imageUrl` and `buttonTitle` (see [discussions/194](https://github.com/farcasterxyz/miniapps/discussions/194))
* Made `url` optional in `actionLaunchFrameSchema` - when not provided, it defaults to the current webpage URL (including query parameters) (see [discussions/189](https://github.com/farcasterxyz/miniapps/discussions/189))
### April 6, 2024 (0.0.34)
* Increased URL max length to 1024 characters
## Context
When your app is opened it can access information about the session from
`sdk.context`. This object provides basic information about the user, the
client, and where your app was opened from:
```ts
export type FrameContext = {
user: {
fid: number;
username?: string;
displayName?: string;
pfpUrl?: string;
};
location?: FrameLocationContext;
client: {
clientFid: number;
added: boolean;
safeAreaInsets?: SafeAreaInsets;
notificationDetails?: FrameNotificationDetails;
};
};
```
### Properties
#### `location`
Contains information about the context from which the Mini App was launched.
```ts
export type CastEmbedLocationContext = {
type: 'cast_embed';
embed: string;
cast: {
fid: number;
hash: string;
};
};
export type NotificationLocationContext = {
type: 'notification';
notification: {
notificationId: string;
title: string;
body: string;
};
};
export type LauncherLocationContext = {
type: 'launcher';
};
export type ChannelLocationContext = {
type: 'channel';
channel: {
/**
* Channel key identifier
*/
key: string;
/**
* Channel name
*/
name: string;
/**
* Channel profile image URL
*/
imageUrl?: string;
};
};
export type LocationContext =
| CastEmbedLocationContext
| NotificationLocationContext
| LauncherLocationContext
| ChannelLocationContext;
```
##### Cast Embed
Indicates that the Mini App was launched from a cast (where it is an embed).
```ts
> sdk.context.location
{
type: "cast_embed",
cast: {
fid: 3621,
hash: "0xa2fbef8c8e4d00d8f84ff45f9763b8bae2c5c544",
}
}
```
##### Notification
Indicates that the Mini App was launched from a notification triggered by the frame.
```ts
> sdk.context.location
{
type: "notification",
notification: {
notificationId: "f7e9ebaf-92f0-43b9-a410-ad8c24f3333b"
title: "Yoinked!",
body: "horsefacts captured the flag from you.",
}
}
```
##### Launcher
Indicates that the Mini App was launched directly by the client app outside of a context, e.g. via some type of catalog or a notification triggered by the client.
```ts
> sdk.context.location
{
type: "launcher"
}
```
#### `user`
Details about the calling user which can be used to customize the interface. This should be considered untrusted since it is passed in by the application, and there is no guarantee that it was authorized by the user.
```ts
export type AccountLocation = {
placeId: string;
/**
* Human-readable string describing the location
*/
description: string;
};
export type UserContext = {
fid: number;
username?: string;
displayName?: string;
/**
* Profile image URL
*/
pfpUrl?: string;
location?: AccountLocation;
};
```
```ts
> sdk.context.user
{
"fid": 6841,
"username": "deodad",
"displayName": "Tony D'Addeo",
"pfp": "https://i.imgur.com/dMoIan7.jpg",
"bio": "Building @warpcast and @farcaster, new dad, like making food",
"location": {
"placeId": "ChIJLwPMoJm1RIYRetVp1EtGm10",
"description": "Austin, TX, USA"
}
}
```
```ts
type User = {
fid: number;
username?: string;
displayName?: string;
pfp?: string;
bio?: string;
location?: {
placeId: string;
description: string;
};
};
```
#### client
Details about the Farcaster client running the Mini App. This should be considered untrusted
* `clientFid`: the self-reported FID of the client (e.g. 9152 for Warpcast)
* `added`: whether the user has added the Mini App to the client
* `safeAreaInsets`: insets to avoid areas covered by navigation elements that obscure the view
* `notificationDetails`: in case the user has enabled notifications, includes the `url` and `token` for sending notifications
```ts
export type SafeAreaInsets = {
top: number;
bottom: number;
left: number;
right: number;
};
export type ClientContext = {
clientFid: number;
added: boolean;
notificationDetails?: FrameNotificationDetails;
safeAreaInsets?: SafeAreaInsets;
};
```
```ts
> sdk.context.client
{
clientFid: 9152,
added: true,
safeAreaInsets: {
top: 0,
bottom: 20,
left: 0,
right: 0,
};
notificationDetails: {
url: "https://api.warpcast.com/v1/frame-notifications",
token: "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
```ts
type FrameNotificationDetails = {
url: string;
token: string;
};
type SafeAreaInsets = {
top: number;
bottom: number;
left: number;
right: number;
};
type ClientContext = {
clientFid: number;
added: boolean;
safeAreaInsets?: SafeAreaInsets;
notificationDetails?: FrameNotificationDetails;
};
```
##### Using safeAreaInsets
Mobile devices render navigation elements that obscure the view of an app. Use
the `safeAreaInsets` to render content in the safe area that won't be obstructed.
A basic usage would to wrap your view in a container that adds margin:
```
...your app view
```
However, you may want to set these insets on specific elements: for example if
you have tab bar at the bottom of your app with a different background, you'd
want to set the bottom inset as padding there so it looks attached to the
bottom of the view.
## Client Events
When a user interacts with your app events will be sent from the Farcaster
client to your application client.
Farcaster clients emit events to directly to your app client while it is open that can
be used to update your UI in response to user actions.
To listen to events, you have to use `sdk.on` to register callbacks ([see full
example](https://github.com/farcasterxyz/frames-v2-demo/blob/20d454f5f6b1e4f30a6a49295cbd29ca7f30d44a/src/components/Demo.tsx#L92-L124)).
Listeners can be cleaned up with `sdk.removeListener()` or sdk.removeAllListeners()\`.
### Events
#### frameAdded
The user added the Mini App.
#### frameRemoved
The user removed the Mini App.
#### notificationsEnabled
The user enabled notifications after previously having them disabled.
#### notificationsDisabled
The user disabled notifications.
import { Caption } from '../../../components/Caption';
## Wallet

A user minting an NFT using the Warpcast Wallet.
The SDK exposes an [EIP-1193 Ethereum Provider
](https://eips.ethereum.org/EIPS/eip-1193) at `sdk.wallet.ethProvider`. You can
interact with this object directly or use it with ecosystem tools like
[Wagmi](https://wagmi.sh/) or [Ethers](https://docs.ethers.org/v6/).
For more information:
* [EIP-1193 Ethereum Provider API](https://eips.ethereum.org/EIPS/eip-1193)
* [Guide on interacting with wallets](/docs/guides/wallets)
import { Caption } from '../../../../components/Caption';
## addFrame
Prompts the user to add the app.

A users discover an app from their social feed, adds it, and then sees it
from their apps screen
### Usage
```ts twoslash
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.addFrame()
```
### Return Value
`void`
### Errors
#### `RejectedByUser`
Thrown if a user rejects the request to add the Mini App.
#### `InvalidDomainManifestJson`
Thrown an app does not have a valid `farcaster.json`.
import { Caption } from '../../../../components/Caption';
## close
Closes the mini app.

Close the app with `close`.
### Usage
```ts twoslash
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.close()
```
### Return Value
`void`
import { Caption } from '../../../../components/Caption';
## composeCast
Open the cast composer with a suggested cast. The user will be able to modify
the cast before posting it.

An app prompts the user to cast and includes an embed.
### Usage
```ts twoslash
/**
* Cryptographically secure nonce generated on the server and associated with
* the user's session.
*/
const text = "I just learned how to compose a cast";
const embeds = ["https://miniapps.farcaster.xyz/docs/sdk/actions/compose-cast"] as [string];
// ---cut---
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.composeCast({
text,
embeds,
})
```
### Parameters
#### text (optional)
* **Type:** `string`
Suggested text for the body of the cast.
Mentions can be included using the human-writeable form (e.g. @farcaster).
#### embeds (optional)
* **Type:** `[] | [string] | [string, string]`
Suggested embeds. Max two.
#### parent (optional)
* **Type:** `{ type: 'cast'; hash: string }`
Suggested parent of the cast.
#### close (optional)
* **Type:** `boolean`
Whether the app should be closed when this action is called. If true the app
will be closed and the action will resolve with no result.
### Return Value
The cast posted by the user, or `undefined` if set to close.
```ts twoslash
import { sdk } from "@farcaster/frame-sdk";
// ---cut---
const result = await sdk.actions.composeCast({
// ^?
text: "I just learned how to compose a cast",
embeds: ["https://miniapps.farcaster.xyz/docs/sdk/actions/compose-cast"]
})
```
import { Caption } from '../../../../components/Caption';
## openUrl
Opens an external URL.
If a user is on mobile `openUrl` can be used to deeplink
users into different parts of the Farcaster client they
are using.

Opening an external url with `openUrl`.
### Usage
```ts twoslash
const url = 'https://farcaster.xyz';
//---cut---
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.openUrl(url)
```
### Return Value
\`void
import { Caption } from '../../../../components/Caption';
## ready
Hides the Splash Screen. Read the [guide on loading your app](/docs/guides/loading) for best practices.

Dismiss the Splash Screen with ready.
### Usage
```ts twoslash
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.ready()
```
### Parameters
#### disableNativeGestures (optional)
* **Type:** `boolean`
* **Default:** `false`
Disable native gestures. Use this option if your frame uses gestures
that conflict with native gestures like swipe to dismiss.
### Return Value
`void`
import { Caption } from '../../../../components/Caption';
## signIn
Request a [Sign in with Farcaster
(SIWF)](https://docs.farcaster.xyz/developers/siwf/) credential from the user.
See the guide on [authenticating users](/docs/guides/auth).

A users opens an app and is automatically signed in
### Usage
```ts twoslash
/**
* Cryptographically secure nonce generated on the server and associated with
* the user's session.
*/
const nonce = 'securenonce';
// ---cut---
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.signIn({
nonce
})
```
### Parameters
#### nonce
* **Type:** `string`
A random string used to prevent replay attacks, at least 8 alphanumeric
characters.
### Return Value
The SIWF message and signature.
```
type SignInResult = {
signature: string;
message: string;
}
```
:::note
This message must be sent to your server and verified. See the guide on
[authenticating with Farcaster](/docs/guides/loading) for more information.
:::
import { Caption } from '../../../../components/Caption';
## viewProfile
Displays a user's Farcaster profile.

Viewing a profile and follow a user from an app.
### Usage
```ts twoslash
const fid = 6841;
// ---cut---
import { sdk } from '@farcaster/frame-sdk'
await sdk.actions.viewProfile({
fid
})
```
### Parameters
#### fid
* **Type:** `number`
Farcaster ID of the user who's profile to view.
### Return Value
`void`