# Farcaster Mini Apps ## Blog ::blog-posts ## Why Farcaster Doesn't Need OAuth 2.0 OAuth exists to let three separate parties (user → platform → third-party app) establish mutual trust. Farcaster is built on a decentralized architecture that collapses this triangle: ### 1. Identity & Authentication * **User-owned keys:** A user controlled crypotgraphic signature proves control of a Farcaster ID—no intermediary. * **Dev mappings** * Sign In with X → Sign-in with Farcaster (SIWF) * OAuth 2.0 Authorization Flow → Quick Auth ### 2. Data Access & Permissions * **Open, replicated data:** Social data like casts, reactions, and profiles live on Snapchain and can be read by anyone. * **No permission scopes:** Everything is already public; you filter what you need instead of requesting scopes. * **Zero-cost reads:** Sync the chain yourself or hit a public indexer—no rate caps, no $5k +/month fire-hoses. * **Cryptographic writes:** Users can delegate a key to applications so the applications can writes on their behalf. * **Dev mappings** * Centralized APIs → Snapchain + infra services (e.g. Neynar) * Access token → no equivalent, data is public * Write permissions → App Keys ### Builder Takeaways 1. **Skip OAuth flows—wallet signature = auth.** 2. **Forget permission scopes—use filters.** 3. **Enjoy building permissionlessly** ### Resources * [Quick Auth](https://miniapps.farcaster.xyz/docs/sdk/quick-auth) * [Neynar SDK for one-call Snapchain queries](https://docs.neynar.com/reference/quickstart) * [App Keys](https://docs.farcaster.xyz/reference/warpcast/signer-requests) ## Getting Started import { Caption } from '../../components/Caption.tsx'; ### 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. ### Requirements Before getting started, make sure you have: * **Node.js 22.11.0 or higher** (LTS version recommended) * Check your version: `node --version` * Download from [nodejs.org](https://nodejs.org/) * A package manager (npm, pnpm, or yarn) :::warning If you encounter installation errors, verify you're using Node.js 22.11.0 or higher. Earlier versions are not supported. ::: ### Quick Start For new projects, you can set up an app using the [@farcaster/create-mini-app](https://github.com/farcasterxyz/miniapps/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 MiniApp SDK: #### Package Manager :::code-group ```bash [npm] npm install @farcaster/miniapp-sdk ``` ```bash [pnpm] pnpm add @farcaster/miniapp-sdk ``` ```bash [yarn] yarn add @farcaster/miniapp-sdk ``` ::: #### CDN If you're not using a package manager, you can also use the MiniApp SDK via an ESM-compatible CDN such as esm.sh. Simply add a ` ``` ### Making Your App Display After your app loads, you must call `sdk.actions.ready()` to hide the splash screen and display your content: ```javascript import { sdk } from '@farcaster/miniapp-sdk' // After your app is fully loaded and ready to display await sdk.actions.ready() ``` :::warning **Important**: If you don't call `ready()`, users will see an infinite loading screen. This is one of the most common issues when building Mini Apps. ::: ### Troubleshooting #### Node.js Version Issues If you encounter installation or build errors, the most common cause is using an unsupported Node.js version. **Common error messages:** * `npm ERR! engine Unsupported platform` * `npm ERR! peer dep missing` * Build failures with cryptic error messages * Package installation failures **Solution:** 1. Check your Node.js version: ```bash node --version ``` 2. If you're using Node.js \< 22.11.0, update to the latest LTS version: * Visit [nodejs.org](https://nodejs.org/) to download the latest LTS * Or use a version manager like `nvm`: ```bash nvm install --lts nvm use --lts ``` [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) ### 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: setup mini app docs in cursor
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. ![mini app embed](/embed_schematic.png) #### 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 MiniAppEmbed in a serialized form in the `fc:miniapp` meta tag in the HTML ``. For backward compatibility, the `fc:frame` meta tag is also supported. 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 "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 | The type of action. | One of: `launch_frame`, `view_token` | | 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": "1", "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 ![https://github.com/user-attachments/assets/66cba3ca-8337-4644-a3ac-ddc625358390](https://github.com/user-attachments/assets/66cba3ca-8337-4644-a3ac-ddc625358390) #### 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. ![splash schematic](/splash_screen_schematic.png) #### 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/miniapp-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 * [addMiniApp](/docs/sdk/actions/add-miniapp) - 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 * [viewCast](/docs/sdk/actions/view-cast) - View a specific cast * [swapToken](/docs/sdk/actions/swap-token) - Prompt the user to swap tokens * [sendToken](/docs/sdk/actions/send-token) - Prompt the user to send tokens * [viewToken](/docs/sdk/actions/view-token) - View a token ##### Wallet * [getEthereumProvider](/docs/sdk/wallet) - [EIP-1193 Ethereum Provider](https://eips.ethereum.org/EIPS/eip-1193) * [getSolanaProvider](/docs/sdk/solana) - Experimental Solana provider #### 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 Manifests 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 | | miniapp (or 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" }, "miniapp": { "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 [addMiniApp](/docs/sdk/actions/add-miniapp) 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: * `miniapp_added` * `miniapp_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. ##### miniapp\_added This event may happen when an open frame calls `actions.addMiniApp` 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": "miniapp-added", "notificationDetails": { "url": "https://api.farcaster.xyz/v1/frame-notifications", "token": "a05059ef2415c67b08ecceb539201cbc6" } } ``` ```ts type EventMiniAppAddedPayload = { event: 'miniapp_added'; notificationDetails?: MiniAppNotificationDetails; }; ``` ##### miniapp\_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": "miniapp-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.farcaster.xyz/v1/frame-notifications", "token": "a05059ef2415c67b08ecceb539201cbc6" } } ``` ```ts type EventNotificationsEnabledPayload = { event: 'notifications_enabled'; notificationDetails: MiniAppNotificationDetails; }; ``` #### 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` ##### Idempotency A host MUST deduplicate notification requests using `(FID, notificationId)` as an idempotency that is valid 24 hours. This allows Apps to safely retry notification requests. ##### 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: ![notifications schematic](/notification_schematic.png) ##### 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 { ManifestSchemaRenderer, SchemaRenderer } from '../../../components/ManifestRender.tsx' import { domainManifestSchema } from '@farcaster/miniapp-sdk' ### Purpose You are an AI agent helping developers troubleshoot Farcaster Mini Apps. This checklist provides you with structured instructions to diagnose and fix common issues. Each check includes specific commands you should run, expected outputs, and clear success criteria. ### General Guidelines As an AI agent, follow these guidelines: * **Ask clarifying questions** until you have high confidence in the task. Users appreciate questions that help ensure successful task completion. * **Be specific** when something is unclear or inaccessible. Ask for file paths, URLs, or specific error messages. * **Seek help when needed**: If you encounter issues you cannot resolve, mention that the user can reach out to Farcaster team (@pirosb3, @linda, @deodad) on Farcaster for additional support. * **Verify assumptions** before making changes. It's better to confirm than to proceed with uncertainty. ### Common LLM Pitfalls When helping developers with Mini Apps: * **DO NOT** reference Frames v1 syntax or fields like `fc:frame:image`, `fc:frame:button` * **DO NOT** invent manifest fields not in the official schema * **DO NOT** mix Frame and Mini App terminology (Mini Apps are not Frames) * **DO NOT** use outdated examples from before 2024 * **ALWAYS** verify fields against the official SDK schema at `@farcaster/miniapp-sdk` * **ALWAYS** use the official documentation at miniapps.farcaster.xyz * **ALWAYS** check that examples use `miniapp` or `frame` (not `frames`) in manifest If using example code, ensure it's from: * Official Mini Apps documentation (this site) * The @farcaster/miniapp-sdk package * Examples that use `"version": "1"` not `"version": "next"` ### Prerequisites Before you begin troubleshooting, ensure the developer understands: * [What Mini Apps are](/docs/getting-started) * [How manifests work](/docs/specification#manifest) * [SDK actions and capabilities](/docs/sdk/actions/ready) *** ### Check 1: Manifest Configuration #### 1.1 Verify Manifest Accessibility **Command:** ```bash curl -s https://{domain}/.well-known/farcaster.json ``` **Expected Output:** ```json { "accountAssociation": { "header": "...", "payload": "...", "signature": "..." }, "frame": { "version": "1", "name": "App Name", "iconUrl": "https://...", "homeUrl": "https://..." } } ``` **Success Criteria:** * HTTP 200 response * Valid JSON format * Contains `accountAssociation` object * Contains `frame` object with required fields **If Check Fails:**
Manifest not found (404) **Decision Flow:** ``` Is hosting available? ├─ Yes: Use hosted manifest │ └─ Direct to: https://farcaster.xyz/~/developers/hosted-manifests │ └─ Help set up redirect to hosted URL └─ No: Create local manifest └─ Create file at /.well-known/farcaster.json ``` **For Vercel redirect:** ```json { "redirects": [ { "source": "/.well-known/farcaster.json", "destination": "https://api.farcaster.xyz/miniapps/hosted-manifest/{manifest-id}", "permanent": false } ] } ```
Manifest exists but unsigned **Action:** Direct the user to sign the manifest * Tool: [https://farcaster.xyz/\~/developers/mini-apps/manifest?domain=\{their-domain}](https://farcaster.xyz/~/developers/mini-apps/manifest?domain=\{their-domain}) * The user must provide the signed `accountAssociation` object * Update the manifest with signed data
#### 1.2 Validate Manifest Schema **Valid Manifest Example:** #### 1.3 Verify Domain Signature **Validation Steps:** 1. Decode the base64url `payload` from `accountAssociation.payload` 2. Extract the `domain` field 3. Verify domain matches where manifest is hosted **Example:** ```javascript // If hosted at www.example.com const payload = JSON.parse(atob(accountAssociation.payload)); // payload.domain should be "www.example.com" (including subdomain) ``` **Important:** The signed domain must match exactly, including subdomains. *** ### Check 2: Embed Metadata #### 2.1 Verify Embed Tags on Entry Points **What to check:** * Root URL of the mini app * All shareable pages (products, profiles, content) **Command:** ```bash curl -s https://{domain}/{path} | grep -E 'fc:miniapp|fc:frame' ``` **Expected Output:** ```html ``` #### 2.2 Validate Embed Structure **For Next.js Applications:** ```typescript // app/layout.tsx or pages with generateMetadata import { Metadata } from 'next' const frame = { version: "1", // Not "next" - must be "1" imageUrl: "https://example.com/og-image.png", // 3:2 aspect ratio button: { title: "Open App", // Max 32 characters action: { type: "launch_frame", name: "My Mini App", url: "https://example.com", // Optional, defaults to current URL splashImageUrl: "https://example.com/icon.png", // 200x200px splashBackgroundColor: "#f7f7f7" } } } export async function generateMetadata({ params }): Promise { return { title: "My Mini App", openGraph: { title: "My Mini App", description: "Description here" }, other: { "fc:miniapp": JSON.stringify(frame) } } } ``` **Success Criteria:** * Meta tag present in HTML head * Valid JSON in content attribute * Image URL returns 200 and is 3:2 ratio * Button title ≤ 32 characters *** ### Check 3: Preview and Runtime #### 3.1 Test in Preview Tool **URL Format:** ``` https://farcaster.xyz/~/developers/mini-apps/preview?url={encoded-mini-app-url} ``` **Example:** ```bash # Encode your URL encoded_url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('https://example.com/page'))") echo "https://farcaster.xyz/~/developers/mini-apps/preview?url=$encoded_url" ``` #### 3.2 Verify App Initialization **Common Issues:**
App not loading (infinite splash screen) **Cause:** App hasn't called [`sdk.actions.ready()`](/docs/sdk/actions/ready) **Solution:** Ensure the app calls ready() after initialization: ```javascript import { sdk } from '@farcaster/miniapp-sdk' // After app is ready to display await sdk.actions.ready() ```
Tunnel URLs not working (ngrok, localtunnel) **Issue:** Browser security blocks unvisited tunnel URLs **Solution:** 1. Open tunnel URL directly in browser first 2. Then use in preview tool 3. This whitelists the domain for iframe usage
*** ### Post-Check Verification After making any changes, you should: 1. **Re-verify the manifest is deployed:** ```bash curl -s https://{domain}/.well-known/farcaster.json | jq . ``` 2. **Test a shareable link:** * Ask the user to share in Farcaster client * Verify embed preview appears * Confirm app launches on click 3. **Monitor for errors:** * Check browser console for SDK errors * Verify no CORS issues * Ensure all assets load (splash image, icon) *** ### Quick Reference | Check | Command | Success Indicator | | --------------- | --------------------------------------------- | ---------------------- | | Manifest exists | `curl -s {domain}/.well-known/farcaster.json` | HTTP 200, valid JSON | | Manifest signed | Decode `payload`, check domain | Domain matches hosting | | Embed present | `curl -s {url} \| grep fc:miniapp` | Meta tag found | | Preview works | Open preview tool URL | App loads, no errors | | App ready | Check console logs | `ready()` called | *** ### Related Documentation * [Getting Started Guide](/docs/getting-started) * [Publishing Guide](/docs/guides/publishing) * [SDK Actions Reference](/docs/sdk/actions/ready) import { Caption } from '../../../components/Caption.tsx'; ## Authenticating users ![signing in a user](/sign_in_preview.png) A user opens an app and is automatically signed in Mini Apps can seamlessly authenticate Farcaster users to create secure sessions. ### Quick Auth The easiest way to get an authenticated session for a user. [Quick Auth](/docs/sdk/quick-auth) uses [Sign in with Farcaster](https://docs.farcaster.xyz/developers/siwf/) under the hood to authenticate the user and returns a standard JWT that can be easily verified by your server and used as a session token. [
Get started with Quick Auth
](/docs/sdk/quick-auth) ### Sign In with Farcaster Alternatively, an app can use the [signIn](/docs/sdk/actions/sign-in) to get a [Sign in with Farcaster](https://docs.farcaster.xyz/developers/siwf/) authentication credential for the user. After requesting the credential, applications must 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. ### Enable seamless sign in on web Farcaster recently added support for signing in via additional wallets (see the [Auth Address](https://github.com/farcasterxyz/protocol/discussions/225) standard). If you are using Quick Auth no action is needed. If you are using `signIn` directly you will need to make a couple changes to support signing in with Auth Addresses: :::steps #### Accept auth addresses Update `@farcaster/miniapp-sdk` to version `0.0.39` or later. Opt in to auth address sign in by passing `acceptAuthAddress: true` to the `signIn` action: ```ts import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.signIn({ nonce, acceptAuthAddress: true }); ``` ::: Farcaster client developers can find more information [here](https://www.notion.so/warpcast/Public-Auth-Address-Implementation-Guide-1fc6a6c0c10180a9b2a7f24c71143eae). #### Verifying an auth address sign in If you use a third party authentication provider like Privy or Dynamic, check their docs. You’ll likely need to update your dependencies. If you verify sign in messages yourself, update the `@farcaster/auth-client` package to version `0.7.0` or later. Calling `verifySignInMessage` will now verify signatures from a custody or auth address. import { Caption } from '../../../components/Caption.tsx'; ## App Discovery & Search Making your Mini App discoverable is crucial for reaching users in the Farcaster ecosystem. This guide covers how to ensure your app is correctly indexed and visible in our mini apps catalogue. ### Making Your App Discoverable in Farcaster Apps appear in the [main directory](https://farcaster.xyz/miniapps) and search engine on [Farcaster](https://farcaster.xyz). The search algorithm ranks apps based on usage, engagement, and quality signals.
Search results showing Mini Apps
Mini Apps appear alongside users in Farcaster search results, showing app name, icon, and creator. For your Mini App to be properly indexed and discoverable, several criteria must be met: #### App Registration * **Register your manifest**: Your app must be registered with Farcaster using the [manifest tool](https://farcaster.xyz/~/developers/mini-apps/manifest). Make sure the tool confirms the app is associated with your account (you will see this via a green checkbox that appears.) * **Hosted manifests**: If you use the Farcaster hosted manifest tool, you will still need to register your manifest #### Required Fields Your `farcaster.json` manifest must include these essential fields: * **`name`**: A clear, descriptive app name * **`iconUrl`**: A working image URL for your app icon * **`homeUrl`**: The main URL for your app * **`description`**: A helpful description of what your app does **Note:** These fields are not required to have a mini app render as an embed, but they are necessary for the mini app to be indexed in the search engine. #### Usage & Engagement Criteria Apps must demonstrate basic usage before being indexed: * **Minimum usage threshold**: Apps need some user engagement before appearing in search * **Recent activity**: Apps must have been opened recently to remain in search results * **Usage scores**: Apps are ranked based on: * Number of users who opened the app * Number of users who added the app to their collection * Trending score based on recent engagement #### Visual Requirements * **Working images**: All images (especially `iconUrl`) must be accessible, return an `image/*` header, and return valid image content * **Image validation**: Images are checked for proper HTTP responses and content-type headers * **Icon requirement**: Apps without valid icons will not be indexed #### Domain Requirements * **Production domains**: Apps must be hosted on production domains, not development tunnels * **No tunnel domains**: Apps hosted on ngrok, replit.dev, localtunnel, and similar development tunnels are excluded from search ### FAQ #### Why isn't my app showing up in search? For your Mini App to appear in search results, it must meet several criteria: * **App indexing enabled**: Ensure your app doesn't have `noindex: true` set in your manifest * **Manifest registered**: Your app must be registered with Farcaster using the [manifest tool](https://farcaster.xyz/~/developers/mini-apps/manifest) * **Recent usage**: Your app needs active users and recent opens to stay in search results * **Usage thresholds**: Meet minimum engagement requirements for opens, adds, or trending activity * **Working images**: Your `iconUrl` must be accessible and return valid image content * **Complete manifest**: Required fields (`name`, `iconUrl`, `homeUrl`, `description`) must be filled out * **Production domain**: Apps hosted on development tunnels (ngrok, replit.dev, etc.) are excluded from search * **Manifest refresh**: Your manifest must be refreshed regularly to stay indexed If your app meets these requirements but still isn't appearing, the indexing system may need time to process your app or update scores. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) #### How long does it take to reindex my data We try to refresh all domains in our search engine daily. #### How does the trending score work? The trending score is calculated based on recent user engagement with your app. Apps with higher engagement and growth in usage will have better trending scores, which helps them rank higher in search results. #### Can I improve my app's search ranking? Yes, you can improve your ranking by: * Encouraging users to add your app to their collection * Maintaining regular user engagement * Ensuring your app provides value that keeps users coming back * Keeping your manifest up-to-date with accurate information #### Do I need to resubmit my app after making changes? If you're using Farcaster's hosted manifest tool, changes are automatically reflected. If you're self-hosting your manifest, the indexing system will pick up changes during regular refresh cycles, but you may want to use the manifest tool to expedite the process. import { Caption } from '../../../components/Caption.tsx'; ## Migrating to a new domain While Mini Apps are designed to be associated with a stable domain, there are times when you may need to migrate your app to a new domain. This could be due to rebranding, domain expiration, or other business reasons. The `canonicalDomain` field enables a smooth transition by allowing you to specify the new domain in your old manifest, ensuring clients can discover and redirect to your app's new location. ### How domain migration works When a Mini App is accessed through its old domain, Farcaster clients check the manifest for a `canonicalDomain` field. If present, clients will: 1. Recognize that the app has moved to a new domain 2. Update their references to point to the new domain 3. Redirect users to the app at its new location This ensures continuity for your users and preserves your app's presence in app stores and user installations. ### Migration steps ::::steps #### Prepare your new domain Set up your Mini App on the new domain with a complete manifest file at `/.well-known/farcaster.json`. This should include all your app configuration and an account association from the same FID to maintain ownership verification. ```json { "accountAssociation": { "header": "...", "payload": "...", "signature": "..." }, "miniapp": { "version": "1", "name": "Your App Name", "iconUrl": "https://new-domain.com/icon.png", "homeUrl": "https://new-domain.com", // ... other configuration } } ``` #### Update the old domain manifest Add the `canonicalDomain` field to your manifest on the **old domain**, pointing to your new domain: ```json { "accountAssociation": { "header": "...", "payload": "...", "signature": "..." }, "miniapp": { "version": "1", "name": "Your App Name", "iconUrl": "https://old-domain.com/icon.png", "homeUrl": "https://old-domain.com", "canonicalDomain": "new-domain.com", // Add this line // ... other configuration } } ``` :::note The `canonicalDomain` value must be a valid domain name without protocol, port, or path: * ✅ `app.new-domain.com` * ✅ `new-domain.com` * ❌ `https://new-domain.com` * ❌ `new-domain.com:3000` * ❌ `new-domain.com/app` ::: #### Optional: Add canonicalDomain to the new manifest You can optionally include the `canonicalDomain` field in your new domain's manifest as well, pointing to itself. This can help with client caching and ensures consistency: ```json { "accountAssociation": { "header": "...", "payload": "...", "signature": "..." }, "miniapp": { "version": "1", "name": "Your App Name", "iconUrl": "https://new-domain.com/icon.png", "homeUrl": "https://new-domain.com", "canonicalDomain": "new-domain.com", // Self-referential // ... other configuration } } ``` #### Maintain both domains during transition Keep both domains active during the migration period to ensure a smooth transition: * Continue serving your app from the old domain with redirects to the new domain * Keep the manifest file accessible on both domains * Monitor traffic to understand when most users have migrated #### Implement redirects (recommended) While the `canonicalDomain` field helps Farcaster clients understand the migration, you should also implement HTTP redirects from your old domain to the new one for users accessing your app directly after the manifest changes have been retrieved by the clients: ```js // Example redirect in Express app.get('*', (req, res) => { const newUrl = `https://new-domain.com${req.originalUrl}`; res.redirect(301, newUrl); }); ``` :::: ### Best practices #### Plan ahead * Choose a stable domain from the start to minimize the need for migrations * If you anticipate a rebrand, consider using a neutral domain that can outlast brand changes #### Communicate the change * Notify your users about the domain change through in-app messages or casts * Update any documentation or links that reference your old domain #### Test thoroughly * Verify that your manifest is correctly served on both domains * Test the migration flow in different Farcaster clients * Ensure all app functionality works correctly on the new domain #### Monitor the transition * Track traffic on both domains to understand migration progress * Keep the old domain active until traffic drops to negligible levels * Consider setting up analytics to track successful redirects ### Troubleshooting [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) #### Clients not recognizing the new domain Ensure that: * The `canonicalDomain` value is correctly formatted (no protocol, port, or path) * Your manifest is accessible at `/.well-known/farcaster.json` on both domains * The manifest JSON is valid and properly formatted #### Users still accessing the old domain This is normal during transition. Some clients may cache manifest data, and users may have bookmarked the old URL. Continue to serve redirects from the old domain. #### Account association issues Make sure you use the same account to produce the association on both domains to maintain ownership verification. Do not reuse the account association data from one manifest to the other. ## 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. ![calling ready to hide the splash screen](/ready_preview.png) ### Calling ready Call [ready](/docs/sdk/actions/ready) when your interface is ready to be displayed: #### In React applications If you're using React, call `ready` inside a `useEffect` hook to prevent it from running on every re-render: **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 [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) #### 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. ### Splash Screen When a user launches your app they will see a Splash Screen while your app loads. ![splash screen schematic](/splash_screen_schematic.png) 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://farcaster.xyz/~/developers/mini-apps/debug) on desktop 2. Enter your app url 3. Hit *Preview* :::info You must be logged into your Warpcast account on desktop to access the Mini App Debug Tool. ::: [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) import { Caption } from '../../../components/Caption.tsx'; ## Sending Notifications :::tip Reference: [Notifications Spec](/docs/specification#notifications) ::: Mini Apps can send notifications to users who have added the Mini App to their Farcaster client and enabled notifications. ![in-app notifications in Warpcast](/in-app-notifications-preview.png) 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, their Farcaster client (i.e. Warpcast) will generate a unique notification token and send it to your server * to send a notification to a user, make a request to the Farcaster client'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 ### Terms To make our life easier, let's call: * **Farcaster Client**: An application like Warpcast that is able to display Mini Apps. * **Notification Server**: Your server (see bellow). * **(Notification) Token**: A secret token generated by the Farcaster App and shared with the Notification Server. A token is unique for each (Farcaster Client, Mini App, user Fid) tupple. A notification token is basically a permission that a Farcaster client gives your app (on behalf of a user) to send them notifications. ### Steps ::::steps #### Listen for events You'll need a notification 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. Includes ways to target notifications and send without writing code:
[Setup a managed notifications server with Neynar](https://docs.neynar.com/docs/send-notifications-to-mini-app-users). * **Roll your own** - If you want to host your own server to receive webhooks:
[Follow the Receiving Webhooks guide](#receiving-webhooks). [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) #### 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" }, "miniapp": { "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 [addMiniApp](/docs/sdk/actions/add-miniapp) 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 to `webhookUrl` defined in your `farcaster.json` 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 ![notifications schematic](/notification_schematic.png) 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`. You can safely use the same `notificationId` for all batches. ::: 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 `MiniAppLocationNotificationContext` ```ts export type MiniAppLocationNotificationContext = { 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) ##### Avoid duplicate notifications To avoid duplicate notifications, specify a stable `notificationId` for each notification you send. This identifier is joined with the FID (e.g. `(fid, notificationId)` to create a unique key that is used to deduplicate requests to send a notification over a 24 hour period. For example, if you want to send a daily notification to users you could use `daily-reminder-05-06-2024` as your `notificationId`. Now you can safely retry requests to send the daily reminder notifications within a 24 hour period. ##### 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 ##### miniapp\_added Sent when the user adds the Mini App to their Farcaster client (whether or not this was triggered by an `addMiniApp()` prompt). The optional `notificationDetails` object provides the `token` and `url` if the client equates adding to enabling notifications (Warpcast does this). ##### Payload ```json { "event": "miniapp_added", "notificationDetails": { "url": "https://api.farcaster.xyz/v1/frame-notifications", "token": "a05059ef2415c67b08ecceb539201cbc6" } } ``` ##### miniapp\_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": "miniapp_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.farcaster.xyz/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/miniapp-node`](https://github.com/farcasterxyz/miniapps/tree/main/packages/miniapp-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. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) #### Example ```ts twoslash const requestJson = "base64encodeddata"; // ---cut--- import { ParseWebhookEvent, parseWebhookEvent, verifyAppKeyWithNeynar, } from "@farcaster/miniapp-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/miniapp-node`](https://github.com/farcasterxyz/frames/tree/main/packages/miniapp-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.tsx'; ## 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. ![discover mini apps](/explore-preview.png) 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`. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) For now we'll create an empty file: ```sh touch public/.well-known/farcaster.json ``` ##### Farcaster Hosted Manifests (Now Public!) Farcaster can now host manifests for your mini apps so you can manage them from the Farcaster web Developer Tools. This is now available to everyone! **Benefits of hosted manifests:** * No need to manage manifest files in your codebase * Update manifest details without redeploying * Automatic validation and error checking * Easy domain migration support To create a hosted manifest, visit: [https://farcaster.xyz/\~/developers/mini-apps/manifest](https://farcaster.xyz/~/developers/mini-apps/manifest)
Setting up hosted manifests Instead of serving a `/.well-known/farcaster.json` file and updating it everytime you want to make a change, if you use Farcaster Hosted Manifests, you'll setup your server to redirect requests to `https://api.farcaster.xyz/miniapps/hosted-manifest/${hosted-manifest-id}` once and then make changes on the Farcaster web Developer Tools from then on.
To get your hosted manifest ID: 1. Go to [https://farcaster.xyz/\~/developers/mini-apps/manifest](https://farcaster.xyz/~/developers/mini-apps/manifest) 2. Enter your domain and app details 3. You'll receive a hosted manifest ID 4. Set up the redirect as shown below ##### Setting up redirects All web servers support redirects. The following are examples of how to setup redirects in popular frameworks.
Redirects in Next.js ```ts // next.config.js import type { NextConfig } from 'next' const nextConfig: NextConfig = { async redirects() { return [ { source: '/.well-known/farcaster.json', destination: 'https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890', permanent: false, }, ] }, } export default nextConfig ```
Redirects in Express ```ts const express = require('express') const app = express() app.get('/.well-known/farcaster.json', (req, res) => { res.redirect(307, 'https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890') }) ```
Redirects in Hono ```ts import { Hono } from 'hono' const app = new Hono() app.get('/.well-known/farcaster.json', (c) => { return c.redirect('https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890', 307) }) ```
Redirects in Remix ```ts // app/routes/.well-known/farcaster.json.tsx import { redirect } from '@remix-run/node' export const loader = () => { return redirect('https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890', 307) } export default () => null ```
#### 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 `miniapp` property of the manifest (or `frame` for backward compatibility) and has the following properties: import ManifestAppConfigSchema from "../../../snippets/manifestAppConfigSchema.mdx" Here's an example `farcaster.json` file: ```json { "miniapp": { "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", "requiredChains": [ "eip155:8453" ], "requiredCapabilities": [ "actions.signIn", "wallet.getEthereumProvider", "actions.swapToken" ] } } ``` :::note You can omit `webhookUrl` for now. We'll show you how to set it up in the [sending notifications guide](/docs/guides/notifications). ::: #### Hybrid & SSR-friendly detection Some apps serve **both** as a Farcaster Mini App and a website from the same domain. When you want to fetch specific resources **during server-side rendering (SSR)** or conditionally lazy-load the SDK on the client, add a lightweight flag that only Mini-App launch URLs include **Two suggested patterns** | Pattern | How it looks | Why use it | | -------------------------- | --------------------------------------- | ------------------------------------------ | | **Dedicated path** | `/your/path/.../miniapp` | Easiest to match on the server | | **Well-known query param** | `https://example.com/page?miniApp=true` | Works when a single page serves both modes | :::note Treat these markers as a **best-effort hint, not proof**.\ Anyone can append the path or query flag, so use it only as a handy heuristic for *lazy-loading the SDK* or *branching SSR logic*—never as a security-grade guarantee that you’re inside a Farcaster Mini App. ::: ##### Example ```ts // app/layout.tsx 'use client' import { useEffect } from 'react' export default function RootLayout({ children }: { children: React.ReactNode }) { useEffect(() => { const url = new URL(window.location.href) const isMini = url.pathname.startsWith('/mini') || url.searchParams.get('miniApp') === 'true' if (isMini) { import('@farcaster/miniapp-sdk').then(({ sdk }) => { // Mini-App–specific bootstrap here // e.g. sdk.actions.ready() }) } }, []) return children } ``` On the server you can do the same check to skip expensive Mini App work during SSR. ### 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://farcaster.xyz/~/mini-apps/rewards) that are paid out weekly based on usage and onchain transactions. ::: ![verified author ](/verified_author.png) 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://farcaster.xyz/~/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. ::: [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) Here's an example `farcaster.json` file for the domain `yoink.party` with the account association: ```json { "accountAssociation": { "header": "eyJmaWQiOjkxNTIsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgwMmVmNzkwRGQ3OTkzQTM1ZkQ4NDdDMDUzRURkQUU5NDBEMDU1NTk2In0", "payload": "eyJkb21haW4iOiJyZXdhcmRzLndhcnBjYXN0LmNvbSJ9", "signature": "MHgxMGQwZGU4ZGYwZDUwZTdmMGIxN2YxMTU2NDI1MjRmZTY0MTUyZGU4ZGU1MWU0MThiYjU4ZjVmZmQxYjRjNDBiNGVlZTRhNDcwNmVmNjhlMzQ0ZGQ5MDBkYmQyMmNlMmVlZGY5ZGQ0N2JlNWRmNzMwYzUxNjE4OWVjZDJjY2Y0MDFj" }, "miniapp": { "version": "1", "name": "Rewards", "iconUrl": "https://rewards.warpcast.com/app.png", "splashImageUrl": "https://rewards.warpcast.com/logo.png", "splashBackgroundColor": "#000000", "homeUrl": "https://rewards.warpcast.com", "webhookUrl": "https://client.farcaster.xyz/v1/creator-rewards-notifs-webhook", "subtitle": "Top Warpcast creators", "description": "Climb the leaderboard and earn rewards by being active on Warpcast.", "screenshotUrls": [ "https://rewards.warpcast.com/screenshot1.png", "https://rewards.warpcast.com/screenshot2.png", "https://rewards.warpcast.com/screenshot3.png" ], "primaryCategory": "social", "tags": [ "rewards", "leaderboard", "warpcast", "earn" ], "heroImageUrl": "https://rewards.warpcast.com/og.png", "tagline": "Top Warpcast creators", "ogTitle": "Rewards", "ogDescription": "Climb the leaderboard and earn rewards by being active on Warpcast.", "ogImageUrl": "https://rewards.warpcast.com/og.png" } } ``` :::: import { Caption } from '../../../components/Caption'; ## Share Extensions Share extensions allow your Mini App to appear in the Farcaster share sheet, enabling users to share casts directly to your app. When a user shares a cast to your Mini App, it opens with the cast context, allowing you to provide rich, cast-specific experiences. ![Mini app share extension](/share_extension_preview.png) Mini Apps can receive shared casts through the system share sheet ### How it works When a user views a cast in any Farcaster client, they can tap the share button and select your Mini App from the share sheet. Your app will open with information about the shared cast, including the cast hash, author FID, and the viewer's FID. The entire flow takes just two taps: 1. User taps share on a cast 2. User selects your Mini App from the share sheet Your Mini App then opens with full context about the shared cast, ready to provide a tailored experience. ### Setting up share extensions To enable your Mini App to receive shared casts, add a `castShareUrl` to your manifest: ```json { "appUrl": "https://your-app.com", "icon": "https://your-app.com/icon.png", "castShareUrl": "https://your-app.com/share" } ``` The `castShareUrl` must: * Use HTTPS * Match the domain of your registered Mini App * Be an absolute URL After updating your manifest, refresh your manifest and the share extension will be available to all users who have added your Mini App. ### Receiving shared casts When your Mini App is opened via a share extension, it receives the cast information in two ways: #### 1. URL Parameters (Available immediately) Your `castShareUrl` receives these query parameters: | Parameter | Type | Description | | ----------- | ------ | --------------------------------------------------- | | `castHash` | string | The hex-encoded hash of the shared cast | | `castFid` | number | The FID of the cast author | | `viewerFid` | number | The FID of the user sharing the cast (if logged in) | Example URL: ``` https://your-app.com/share?castHash=0x5415e243853e...&castFid=2417&viewerFid=12152 ``` These parameters are available immediately, even during server-side rendering, allowing you to begin fetching cast data right away. #### 2. SDK Context (Available after initialization) Once your Mini App initializes, the SDK provides enriched cast data through the location context: ```typescript import sdk from '@farcaster/miniapp-sdk'; if (sdk.context.location.type === 'cast_share') { const cast = sdk.context.location.cast; // Access enriched cast data console.log(cast.author.username); console.log(cast.hash); console.log(cast.timestamp); // Access optional fields if available if (cast.channelKey) { console.log(`Shared from /${cast.channelKey}`); } } ``` The cast object includes: ```typescript type MiniAppCast = { author: { fid: number; username?: string; displayName?: string; pfpUrl?: string; }; hash: string; parentHash?: string; parentFid?: number; timestamp?: number; mentions?: MiniAppUser[]; text: string; embeds?: string[]; channelKey?: string; }; ``` ### Implementation example Here's a complete example showing how to handle shared casts in your Mini App: ```typescript import { useEffect, useState } from 'react'; import sdk from '@farcaster/miniapp-sdk'; function App() { const [sharedCast, setSharedCast] = useState(null); const [isShareContext, setIsShareContext] = useState(false); useEffect(() => { // Check if we're in a share context if (sdk.context.location.type === 'cast_share') { setIsShareContext(true); setSharedCast(sdk.context.location.cast); } }, []); if (isShareContext && sharedCast) { return (

Cast from @{sharedCast.author.username}

Analyzing cast {sharedCast.hash}...

{/* Your cast-specific UI here */}
); } // Default app experience return
Your regular app UI
; } ``` ### Real-world example: Degen Stats [Degen Stats](https://farcaster.xyz/miniapps/jrth1IniizBC/degen) demonstrates the power of share extensions. Originally designed to show viewers their own stats, it was quickly upgraded to support share extensions. Now when users share a cast to Degen Stats, it seamlessly displays stats for the cast's author instead of the viewer - a simple but powerful enhancement that took minimal implementation effort. ### Best practices 1. **Handle both contexts**: Design your app to work both as a standalone Mini App and when receiving shared casts. 2. **Fast loading**: Users expect immediate feedback when sharing. Show a loading state while fetching additional cast data. 3. **Graceful fallbacks**: Not all cast fields are guaranteed. Handle missing data gracefully. 4. **Clear value**: Make it obvious why sharing a cast to your app provides value. Users should understand what your app does with the shared cast. 5. **Server-side rendering**: Take advantage of URL parameters for faster initial loads by starting data fetches on the server. ### Testing share extensions During development, you can test share extensions by: 1. Adding your development URL to your manifest 2. Refreshing your manifest 3. Sharing any cast to your Mini App from a Farcaster client 4. Verifying your app receives the correct parameters and context ### Next steps * Learn about [SDK Context](/docs/sdk/context) to understand all available location types * Explore [Compose Cast](/docs/sdk/actions/compose-cast) to let users create casts from your Mini App * Check out [View Cast](/docs/sdk/actions/view-cast) to navigate users to specific casts import { Caption } from '../../../components/Caption.tsx'; ## 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 ![sharing an app in a social feed with a embed](/share_preview.png) 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:miniapp` (or `fc:frame` for backward compatibility) meta tag, and use it to render a rich card. ### Properties ![mini app embed](/embed_schematic.png) #### `version` The string literal `'1'`. #### `imageUrl` The URL of the image that should be displayed. ##### Image Format Requirements **Supported formats:** PNG, JPG, GIF, WebP\ **Recommended:** PNG for best compatibility :::warning **Production Warning**: While SVG may work in preview tools, use PNG for production to ensure compatibility across all Farcaster clients. ::: **Size requirements:** * Aspect ratio: 3:2 * Minimum dimensions: 600x400px * Maximum dimensions: 3000x2000px * File size: Must be less than 10MB * URL length: 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_miniapp'` (or `'launch_frame'` for backward compatibility). #### `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 miniapp = { version: "1", imageUrl: "https://yoink.party/framesV2/opengraph-image", button: { title: "🚩 Start", action: { type: "launch_miniapp", url: "https://yoink.party/framesV2", name:"Yoink!", splashImageUrl: "https://yoink.party/logo.png", splashBackgroundColor:"#f5f0ec" } } } ``` ```html ``` ### Mini App URLs Mini Apps have a canonical URL that can be used to share across social feeds and web sites. The URL format is as follows: `https://farcaster.xyz/miniapps//(/)(?)` Learn how to obtain the Mini App URL and how they work in the [Mini App URLs](/docs/guides/urls) guide. ### Debugging You can use the [Mini App Embed Tool](https://farcaster.xyz/~/developers/mini-apps/embed) in Warpcast to preview a embed. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) 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:miniapp` (and optionally `fc:frame` for backward compatibility) 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 miniapp 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. ### Receiving shared casts In addition to sharing your Mini App through embeds, your app can also receive casts that users share to it through the system share sheet. Learn more in the [Share Extensions](/docs/guides/share-extension) guide. ### 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 service 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. ## Interacting with Solana wallets Mini Apps can interact with a user's Solana wallet without needing to worry about popping open "select your wallet" dialogs or flakey connections. ### Getting Started The SDK enables Mini Apps to interact with a user's Solana wallet through [Wallet Standard](https://github.com/anza-xyz/wallet-standard/). We recommend using [Wallet Adapter](https://github.com/anza-xyz/wallet-adapter/)'s React hooks to interface with Wallet Standard. You may also use [Wallet Standard directly](#using-wallet-standard-directly), or interface with our [low-level Solana provider](#low-level-solana-provider). ::::steps #### Setup Wallet Adapter Use the [Quick Setup (using React) guide](https://github.com/anza-xyz/wallet-adapter/blob/master/APP.md) to setup Wallet Adapter in your project. #### Install the Wallet Standard integration :::code-group ```bash [npm] npm install @farcaster/mini-app-solana ``` ```bash [pnpm] pnpm add @farcaster/mini-app-solana ``` ```bash [yarn] yarn add @farcaster/mini-app-solana ``` ::: #### Render the Farcaster Solana provider In place of `ConnectionProvider` and `WalletProvider` from the Wallet Adapter guide, you should render `FarcasterSolanaProvider` from `@farcaster/mini-app-solana`. This does two things: 1. Importing from `@farcaster/mini-app-solana` has the side effect of triggering the Farcaster wallet to register via Wallet Standard. 2. Sets up Wallet Adapter to automatically select the Farcaster wallet. ```tsx import * as React from 'react'; import { FarcasterSolanaProvider } from '@farcaster/mini-app-solana'; import { useWallet } from '@solana/wallet-adapter-react'; const solanaEndpoint = 'https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY'; function App() { // FarcasterSolanaProvider internally renders ConnectionProvider // and WalletProvider from @solana/wallet-adapter-react return ( ) } ``` #### Use the Wallet Adapter hooks You can now use Wallet Adapter React hooks directly within any component rendered downstream of `FarcasterSolanaProvider`. ```tsx function Content() { const { publicKey } = useWallet(); const solanaAddress = publicKey?.toBase58() ?? ''; return {solanaAddress}; } ``` :::: ### Low-level Solana provider The SDK also exposes a low-level Solana provider at `sdk.wallet.getSolanaProvider()`. This provider is modeled after `window.phantom.solana` and its full API can be found [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/solana.ts). ### Using Wallet Standard directly It's also possible to interface with the user's Solana wallet directly through Wallet Standard, or via Wallet Adapter's "core" (non-React) integrations. In order to do so, it's important that you still import our package in your app entry: ```tsx import '@farcaster/mini-app-solana'; ``` This ensures that the user's Solana wallet registers with Wallet Standard. Also note that if you're using Wallet Adapter without our `FarcasterSolanaProvider` React component, you'll need to select the user's Farcaster wallet before attempting any operations. ### Demo app To see how a working Mini App uses a Solana wallet, check out our demo Mini App [here](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/components/Demo.tsx). ### 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. import { Caption } from '../../../components/Caption.tsx'; ## Mini App URLs Mini Apps have a canonical URL that can be used to share across social feeds and web sites. The URL format is as follows: `https://farcaster.xyz/miniapps//(/)(?)` * The `` is a unique identifier assigned to the Mini App when it is [published](/docs/guides/publishing). * The `` is a kebab-case version of the Mini App name, used to create a more readable URL. * The `` is an optional path appended to the Mini App’s `homeURL` when it is opened. * The `` are optional parameters added to the `homeURL` as a query string when the Mini App is opened. The `` and `` are optional and can be used to navigate to a specific page in the Mini App or pass data to the Mini App. When a user clicks a Mini App URL and is logged in: * **On web**: the Mini App opens in the mini app drawer. * **On mobile**: the browser deep links to the Farcaster app and opens the Mini App. ### Where to find the Mini App URL On the web [Developers page](https://farcaster.xyz/~/developers), click the top-right kebab menu on one of your Mini App cards and select **"Copy link to mini app"**. This copies the Mini App URL to your clipboard. When the Mini App is open, tap on the top-right kebab menu and select **"Copy link"** to copy the Mini App URL to your clipboard. ![Copy link to mini app](/copy_link_to_miniapp.png) Copy link to mini app on the Developers page or the Mini App screen. ### How to control what is displayed when I share a Mini App URL Farcaster automatically generates OpenGraph meta tags for Mini App URLs, ensuring they render correctly when shared on social platforms or web apps that support embedded link previews, such as X. To make sure your Mini App displays as intended, include the `fc:frame` meta tag on all Mini App URLs (see ["Sharing your app"](/docs/guides/sharing)) and add all relevant fields in your [application config](/docs/guides/publishing#define-your-application-configuration), especially `ogTitle`, `ogDescription` and `ogImageUrl`. ### How Mini App sub-paths and query params work Each Mini App defines a `homeUrl` property in its [application config](/docs/guides/publishing#define-your-application-configuration). When a user clicks on a Mini App in the Farcaster client's Mini App explorer, a WebView (on mobile) or iframe (on web) pointing to the `homeUrl` is opened. If you share a Mini App URL with a sub-path and/or query parameters, those are appended to the `homeUrl`'s path and query string. For example, if the `homeUrl` is `https://example.com/miniapp/v1` and the Mini App URL is `https://farcaster.xyz/miniapps/12345/example-miniapp/leaderboard?sort=points`, the WebView or iframe will load `https://example.com/miniapp/v1/leaderboard?sort=points`. ### FAQ **Is there another way to get a Mini App's id?** Not at the moment. **When copying the link from the Mini App header, it doesn't copy the Mini App URL, why is that?** Any URL with a valid `fc:frame` meta tag shared in a cast will be treated as a Mini App. Copying the link from these Mini Apps will copy the original URL shared in the cast, not the canonical Mini App URL. **Can I add a sub-path or query params to the Mini App URL copied from the Mini App header?** Not at the moment. Only the canonical Mini App URL or URL shared in the cast will be copied. **Can I open a Mini App from another Mini App?** Yes, you can open a Mini App from another Mini App by using the `openUrl` action. However, this prompts to close the current app when the new app is opened and there is no way to navigate back. ```ts import { sdk } from '@farcaster/miniapp-sdk' const url = 'https://farcaster.xyz/miniapps/deoWzfI9kLjH/rewards' await sdk.actions.openUrl(url) ``` import { Caption } from '../../../components/Caption.tsx'; ## Interacting with Ethereum wallets Mini Apps can interact with a user's EVM wallet without needing to worry about popping open "select your wallet" dialogs or flakey connections. ![users taking onchain action from app](/transaction-preview.png) 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.getEthereumProvider()`. We recommend using [Wagmi](https://wagmi.sh) to connect to and interact with the user's 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/miniapp-wagmi-connector ``` ```bash [pnpm] pnpm add @farcaster/miniapp-wagmi-connector ``` ```bash [yarn] yarn add @farcaster/miniapp-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 { farcasterMiniApp as miniAppConnector } from '@farcaster/miniapp-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. ### Using Neynar to build mini apps *Neynar is a Farcaster developer platform offering a range of services from nodes and APIs to mini app stack.* #### Mini app stack * **Mini app starter kit** - Type `npx @neynar/create-farcaster-mini-app@latest` in your terminal to get started. See [here](https://docs.neynar.com/docs/create-farcaster-miniapp-in-60s) for more information. * **Send notifications to mini app users** - Notification server to send notifications over API or from portal. Includes batching, targeting, etc. Read more [here](https://docs.neynar.com/docs/send-notifications-to-mini-app-users). * **Convert existing web app to mini app** - Follow guide [here](https://docs.neynar.com/docs/convert-web-app-to-mini-app). * **Fetch mini apps by categories** - See API [here](https://docs.neynar.com/reference/fetch-frame-catalog) * **Fetch relevant mini apps for a given user** - See API [here](https://docs.neynar.com/reference/fetch-frame-relevant) * **Search mini app namespace** - See API [here](https://docs.neynar.com/reference/search-frames) * **Crawl mini app metadata** - See API [here](https://docs.neynar.com/reference/fetch-frame-meta-tags-from-url) #### Use with AI Set up Neynar with MCP server and llms.txt - See instructions [here](https://docs.neynar.com/docs/neynar-farcaster-with-cursor/docs/neynar-farcaster-with-cursor). *Note: LLMs hallucinate, you will get best results by passing in links to specific docs and references.* #### Links [Website](https://neynar.com), [Docs](https://docs.neynar.com) ## Back Navigation Integrate with a back navigation control provided by the Farcaster client. ### Usage If your application is already using [browser-based navigation](#web-navigation-integration), you can integrate in one line with: ```ts await sdk.back.enableWebNavigation(); ``` That's it! When there is a page to go back to a [back control](#back-control) will be made available to the user. Otherwise, you can set a custom back handler and show the back control: ```ts sdk.back.onback = () => { // trigger back in your app } await sdk.back.show(); ``` ### Back control The back control will vary depending on the user's device and platform but will generally follow: * a clickable button in the header on web * a horizontal swipe left gesture on iOS * the Android native back control on Android which could be a swipe left gesture combined with a virtual or physical button depending on the device ### Web Navigation integration The SDK can automatically integrate with web navigation APIs. #### `enableWebNavigation()` Enables automatic integration with the browser's navigation system. This will: * Use the modern Navigation API when available; the back button will automatically be shown and hidden based on the value of `canGoBack`. * Fall back to the History API in browsers where Navigation is [not supported](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility) ; the back button will always be shown. ```ts await sdk.back.enableWebNavigation(); ``` #### `disableWebNavigation()` Disables web navigation integration. ```ts await sdk.back.disableWebNavigation(); ``` ### Properties #### `enabled` * **Type**: `boolean` * **Description**: Whether back navigation is currently enabled #### `onback` * **Type**: `() => unknown` * **Description**: Function to call when a back event is triggered. You don't need to set this when using `enableWebNavigation`. ### Methods #### `show()` Makes the back button visible. ```ts await sdk.back.show(); ``` #### `hide()` Hides the back button. ```ts await sdk.back.hide(); ``` ### Events When a user triggers the back control the SDK will emit an `backNavigationTriggered` event. You can add an event listener on `sdk` or use `sdk.back.onback` to respond to these events. If you are using `enableWebNavigation` this event will automatically be listened to and trigger the browser to navigate. Otherwise you should listen for this event and respond to it as appropriate for your application. ### Availability You can check whether the Farcaster client rendering your app supports a back control: ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' const capabilities = await sdk.getCapabilities() if (capabilities.includes('back')) { await sdk.back.enableWebNavigation(); } else { // show a back button within your app } ``` ### Example: Web Navigation ```ts import { useEffect } from 'react'; function App() { useEffect(() => { // Enable web navigation integration sdk.back.enableWebNavigation(); }, []); return (
{/* Your app content */}
); } ``` ### Example: Manual ```ts function NavigationExample() { const [currentPage, setCurrentPage] = useState('home'); useEffect(() => { // Update back button based on current page if (currentPage === 'home') { sdk.back.show(); } else { sdk.back.hide(); } }, [currentPage]); const handleBack = () => { if (currentPage !== 'home') { setCurrentPage('home'); } }; // Listen for back navigation events useEffect(() => { sdk.on('backNavigationTriggered', handleBack); return () => sdk.off('backNavigationTriggered', handleBack); }, [currentPage]); return (
{currentPage === 'home' ? ( ) : ( )}
); } ``` ## What's New ### June 9, 2025 (0.0.61) * Moved Quick Auth out of experimental and enhanced functionality: * Use `sdk.quickAuth.getToken()` in place of `sdk.experimental.quickAuth()`. `getToken` will store the token in memory and return if it not expired, otherwise a new token will be fetched. Developers no longer need to manage keeping this token around or checking expiration and can make calls to `getToken` whenever needed. * Added `fetch` which is a wrapper around the browser Fetch API that adds a Quick Auth token as a Bearer token in the `Authorization` header. ### June 6, 2025 (0.0.59) * Added [`cast_share`](/docs/guides/share-extension) location type for [share extensions](/docs/guides/share-extension), enabling Mini Apps to receive shared casts from the system share sheet * Extended the cast object in `cast_embed` and `cast_share` contexts to include comprehensive metadata (author details, timestamps, mentions, embeds, channel) ### June 4, 2025 (0.0.56) * Added [`back`](/docs/sdk/back) SDK API for integrating back control * Added [`haptics`](/docs/sdk/haptics) SDK methods for triggering haptic feedback (impact, notification, and selection) ### June 1, 2025 (0.0.52) * Added [`viewCast`](/docs/sdk/actions/view-cast) action to open a specific cast in the Farcaster client * Added `channelKey` parameter to [`composeCast`](/docs/sdk/actions/compose-cast) action * Updated `composeCast` result to allow `null` cast when user cancels ### May 21, 2025 (0.0.49) * Introduced [Wallet Standard integration](/docs/guides/solana) for Solana wallets * Moved Solana provider to `wallet.getSolanaProvider()`. Will remain accessible at `experimental.getSolanaProvider()` for a couple versions ### May 20, 2025 (0.0.48) * Added experimental support for `quickAuth`. ### May 16, 2025 (0.0.45) * Added experimental support for [Solana](/docs/guides/solana) * Added optional `requiredChains` / `requiredCapabilities` parameters to [manifest](/docs/guides/publishing#host-a-manifest-file) * Added `getChains` / `getCapabilities` SDK methods to [detect host capabilities](/docs/sdk/detecting-capabilities) * Replaced `wallet.ethProvider` SDK getter with `wallet.getEthereumProvider()` method * Replaced `actions.addFrame()` SDK method with `actions.addMiniApp()` method ### May 2, 2025 (0.0.38) * Added [`isInMiniApp`](/docs/sdk/is-in-mini-app) function to reliably detect Mini App environments ### April 30, 2025 (0.0.37) * Added experimental actions for [`swapToken`](/docs/sdk/actions/swap-token), [`sendToken`](/docs/sdk/actions/send-token), and [`viewToken`](/docs/sdk/actions/view-token) ### 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 MiniAppContext = { user: { fid: number; username?: string; displayName?: string; pfpUrl?: string; }; location?: MiniAppLocationContext; client: { clientFid: number; added: boolean; safeAreaInsets?: SafeAreaInsets; notificationDetails?: MiniAppNotificationDetails; }; }; ``` ### Properties #### `location` Contains information about the context from which the Mini App was launched. ```ts export type MiniAppUser = { fid: number; username?: string; displayName?: string; pfpUrl?: string; }; export type MiniAppCast = { author: MiniAppUser; hash: string; parentHash?: string; parentFid?: number; timestamp?: number; mentions?: MiniAppUser[]; text: string; embeds?: string[]; channelKey?: string; }; export type CastEmbedLocationContext = { type: 'cast_embed'; embed: string; cast: MiniAppCast; }; export type CastShareLocationContext = { type: 'cast_share'; cast: MiniAppCast; }; 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 | CastShareLocationContext | 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", embed: "https://myapp.example.com", cast: { author: { fid: 3621, username: "alice", displayName: "Alice", pfpUrl: "https://example.com/alice.jpg" }, hash: "0xa2fbef8c8e4d00d8f84ff45f9763b8bae2c5c544", timestamp: 1749160866000, mentions: [], text: "Check out this awesome mini app!", embeds: ["https://myapp.example.com"], channelKey: "farcaster" } } ``` ##### Cast Share Indicates that the Mini App was launched when a user shared a cast to your app (similar to sharing content to an app on mobile platforms). ```ts > sdk.context.location { type: "cast_share", cast: { author: { fid: 12152, username: "pirosb3", displayName: "Daniel - Bountycaster", pfpUrl: "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/7229dfa5-4873-42d0-9dd0-69f4f3fc4d00/original" }, hash: "0x1177603a7464a372fc358a7eabdeb70880d81612", timestamp: 1749160866000, mentions: [], text: "Sharing this interesting cast with you!", embeds: ["https://frames-v2.vercel.app/"], channelKey: "staging" } } ``` ##### 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" } ``` [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) ##### Cast Object Structure When your Mini App is launched from a cast context (either `cast_embed` or `cast_share`), you receive a comprehensive cast object with the following metadata: * **author**: The user who created the cast, including their FID, username, display name, and profile picture * **hash**: The unique identifier for the cast * **parentHash** (optional): If this is a reply, the hash of the parent cast * **parentFid** (optional): If this is a reply, the FID of the parent cast author * **timestamp** (optional): Unix timestamp in milliseconds when the cast was created * **mentions** (optional): Array of users mentioned in the cast * **embeds** (optional): Array of URLs embedded in the cast * **channelKey** (optional): The channel where the cast was posted #### `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?: MiniAppNotificationDetails; safeAreaInsets?: SafeAreaInsets; }; ``` ```ts > sdk.context.client { clientFid: 9152, added: true, safeAreaInsets: { top: 0, bottom: 20, left: 0, right: 0, }; notificationDetails: { url: "https://api.farcaster.xyz/v1/frame-notifications", token: "a05059ef2415c67b08ecceb539201cbc6" } } ``` ```ts type MiniAppNotificationDetails = { url: string; token: string; }; type SafeAreaInsets = { top: number; bottom: number; left: number; right: number; }; type ClientContext = { clientFid: number; added: boolean; safeAreaInsets?: SafeAreaInsets; notificationDetails?: MiniAppNotificationDetails; }; ``` ##### 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. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) **Note:** For more fine-grained capability detection, use the [`getCapabilities()`](/docs/sdk/detecting-capabilities#getcapabilities) method which returns specific SDK methods supported by the host. Example usage with `getCapabilities()`: ```ts import { sdk } from '@farcaster/miniapp-sdk' // Get list of supported capabilities const capabilities = await sdk.getCapabilities() // Check if specific haptic methods are supported if (capabilities.includes('haptics.impactOccurred')) { // Impact haptic feedback is available await sdk.haptics.impactOccurred('medium') } ``` ## Detecting chains & capabilities Mini Apps are rendered within "hosts" inside web and mobile apps. Not all hosts support the same feature set, but some Mini Apps might require specific features. If your Mini App requires a given feature, you can declare that feature in your manifest. Alternately, if your Mini App optionally supports a given feature, it can detect the supported set of features at runtime. ### Declaring requirements in your manifest If your Mini App relies on certain blockchains or SDK methods, you can declare those in your manifest via the properties `requiredChains` and `requiredCapabilities`. #### `requiredChains` `miniapp.requiredChains` is an optional [manifest](/docs/guides/publishing#host-a-manifest-file) property that contains an array of [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) identifiers. If the host does not support all of the chains declared here, it will know not to try rendering your Mini App. Note that only the chains listed in `chainList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/schemas/manifest.ts) are supported. If your manifest omits `requiredChains`, then the mini app host will assume that no chains are required. #### `requiredCapabilities` `miniapp.requiredCapabilities` is an optional [manifest](/docs/guides/publishing#host-a-manifest-file) property that contains an array of paths to SDK methods, such as `wallet.getEthereumProvider` or `actions.composeCast`. If the host does not support all of the capabilities declared here, it will know not to try rendering your Mini App. The full list of supported SDK methods can be found in `miniAppHostCapabilityList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/types.ts). If your manifest omits `requiredCapabilities`, then the mini app host will assume that no capabilities are required. ### Runtime detection If your Mini App optionally supports certain blockchains or SDK methods, you can detect whether they are supported at runtime via SDK calls. #### `getChains` This SDK method returns a list of supported blockchains as an array of [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) identifiers. #### `getCapabilities` This SDK method returns a list of supported SDK methods as an array of paths to those SDK methods. The full list of supported SDK methods can be found in `miniAppHostCapabilityList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/frame-core/src/types.ts). ##### Example ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' // Get all supported capabilities const capabilities = await sdk.getCapabilities() // Check for specific capabilities const supportsCompose = capabilities.includes('actions.composeCast') const supportsWallet = capabilities.includes('wallet.getEthereumProvider') // Check for haptics support const supportsHaptics = { impact: capabilities.includes('haptics.impactOccurred'), notification: capabilities.includes('haptics.notificationOccurred'), selection: capabilities.includes('haptics.selectionChanged') } // Use capabilities conditionally if (supportsHaptics.impact) { await sdk.haptics.impactOccurred('medium') } ``` ## 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 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()\`. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) ### Events #### miniappAdded The user added the Mini App. #### miniappRemoved 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.tsx'; ## Haptics Provides haptic feedback to enhance user interactions through physical sensations. The haptics API includes three methods for different types of feedback: impact, notification, and selection. ### Usage ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' // Trigger impact feedback await sdk.haptics.impactOccurred('medium') // Trigger notification feedback await sdk.haptics.notificationOccurred('success') // Trigger selection feedback await sdk.haptics.selectionChanged() ``` ### Methods #### impactOccurred Triggers impact feedback, useful for simulating physical impacts. ##### Parameters ##### type * **Type:** `'light' | 'medium' | 'heavy' | 'soft' | 'rigid'` The intensity and style of the impact feedback. * `light`: A light impact * `medium`: A medium impact * `heavy`: A heavy impact * `soft`: A soft, dampened impact * `rigid`: A sharp, rigid impact ##### Example ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' // Trigger when user taps a button await sdk.haptics.impactOccurred('light') // Trigger for more significant actions await sdk.haptics.impactOccurred('heavy') ``` #### notificationOccurred Triggers notification feedback, ideal for indicating task outcomes. ##### Parameters ##### type * **Type:** `'success' | 'warning' | 'error'` The type of notification feedback. * `success`: Indicates a successful operation * `warning`: Indicates a warning or caution * `error`: Indicates an error or failure ##### Example ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' // After successful action await sdk.haptics.notificationOccurred('success') // When showing a warning await sdk.haptics.notificationOccurred('warning') // On error await sdk.haptics.notificationOccurred('error') ``` #### selectionChanged Triggers selection feedback, perfect for UI element selections. ##### Example ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' // When user selects an item from a list await sdk.haptics.selectionChanged() // When toggling a switch await sdk.haptics.selectionChanged() ``` ### Return Value All haptic methods return `Promise`. ### Availability Haptic feedback availability depends on the client device and platform. You can check if haptics are supported using the `getCapabilities()` method: ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' const capabilities = await sdk.getCapabilities() // Check if specific haptic methods are supported if (capabilities.includes('haptics.impactOccurred')) { await sdk.haptics.impactOccurred('medium') } if (capabilities.includes('haptics.notificationOccurred')) { await sdk.haptics.notificationOccurred('success') } if (capabilities.includes('haptics.selectionChanged')) { await sdk.haptics.selectionChanged() } ``` ### Best Practices 1. **Use sparingly**: Overuse of haptic feedback can be distracting 2. **Match intensity to action**: Use light feedback for minor actions, heavy for significant ones 3. **Provide visual feedback too**: Not all devices support haptics 4. **Check availability**: Always verify haptic support before using 5. **Consider context**: Some users may have haptics disabled in their device settings ## isInMiniApp Determines if the current environment is a Mini App context by analyzing both environment characteristics and communication capabilities. ### Usage ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' // Check if running in a Mini App const isMiniApp = await sdk.isInMiniApp() if (isMiniApp) { // Mini App-specific code } else { // Regular web app code } ``` ### Parameters #### timeoutMs (optional) * **Type:** `number` * **Default:** `100` Optional timeout in milliseconds for context verification. If the context doesn't resolve within this time, the function assumes it's not in a Mini App environment. ### Return Value * **Type:** `Promise` Returns a promise that resolves to `true` if running in a Mini App context, or `false` otherwise. ### Details The function uses a multi-step approach to detect Mini App environments: 1. **Fast Short-Circuit:** Returns `false` immediately in certain scenarios: * During server-side rendering * When neither in an iframe nor in ReactNative WebView 2. **Context Verification:** For potential Mini App environments (iframe or ReactNative WebView), verifies by checking for context communication. 3. **Result Caching:** Once confirmed to be in a Mini App, the result is cached for faster subsequent calls. This approach ensures accurate detection while optimizing performance. :::tip Need to branch during **server-side rendering**? See the **Hybrid & SSR-friendly detection** subsection in the [Publishing guide](/docs/guides/publishing#hybrid-detection). ::: ## Wallet The SDK enables Mini Apps to interact with a user's Solana wallet through [Wallet Standard](https://github.com/anza-xyz/wallet-standard/). Mini apps written in React can use [Wallet Adapter](https://github.com/anza-xyz/wallet-adapter/)'s React hooks, which are sort of like Solana's equivalent of Wagmi. Wallet Adapter also exposes a more low-level interface for non-React apps. For more information: * [Wallet Adapter docs](https://anza-xyz.github.io/wallet-adapter/) * [Guide on interacting with Solana wallets](/docs/guides/solana) import { Caption } from '../../../components/Caption.tsx'; ## Ethereum wallet ![users taking onchain action from app](/transaction-preview.png) 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.getEthereumProvider()`. 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 Ethereum wallets](/docs/guides/wallets) import { Caption } from '../../../../components/Caption.tsx'; ## addMiniApp Prompts the user to add the app. ![adding a mini app in Warpcast](/add_frame_preview.png) A user discovers an app from their social feed, adds it, and then sees it from their apps screen ### Usage ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.addMiniApp() ``` ### Return Value `void` ### Errors #### `RejectedByUser` Thrown if a user rejects the request to add the Mini App. #### `InvalidDomainManifestJson` Thrown when an app does not have a valid `farcaster.json`. import { Caption } from '../../../../components/Caption.tsx'; ## close Closes the mini app. ![closing the app](/close_preview.png) Close the app with `close`. ### Usage ```ts twoslash import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.close() ``` ### Return Value `void` import { Caption } from '../../../../components/Caption.tsx'; ## composeCast Open the cast composer with a suggested cast. The user will be able to modify the cast before posting it. ![composing a cast](/compose_cast_action.png) 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/miniapp-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. #### channelKey (optional) * **Type:** `string` Whether the cast should be posted to a channel. ### Return Value The cast posted by the user, or `undefined` if set to close. **Note:** The `cast` property in the result can be `null` if the user decides not to create the cast. ```ts twoslash import { sdk } from "@farcaster/miniapp-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"], channelKey: "farcaster" // optional channel }) // result.cast can be null if user cancels if (result?.cast) { console.log(result.cast.hash) console.log(result.cast.channelKey) // includes channel if posted to one } ``` import { Caption } from '../../../../components/Caption.tsx'; ## 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 a url](/open_url_preview.png) Opening an external url with `openUrl`. ### Usage ```ts twoslash const url = 'https://farcaster.xyz'; //---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.openUrl(url) ``` ### Return Value `void` import { Caption } from '../../../../components/Caption.tsx'; ## ready Hides the Splash Screen. Read the [guide on loading your app](/docs/guides/loading) for best practices. [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) ![calling ready to hide the splash screen](/ready_preview.png) Dismiss the Splash Screen with ready. ### Usage ```ts twoslash import { sdk } from '@farcaster/miniapp-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` ## sendToken Open the send form with a pre-filled token and recipient. The user will be able to modify the send before executing the transaction. ### Usage ```ts twoslash const token = "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const amount = "1000000"; const recipientFid = 3; // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.sendToken({ token, amount, recipientFid, }) ``` ### Parameters #### token (optional) * **Type:** `string` CAIP-19 asset ID For example, Base USDC: eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 #### amount (optional) * **Type:** `string` Send token amount, as numeric string For example, 1 USDC: 1000000 #### recipientAddress (optional) * **Type:** `string` Address to send the token to For example, 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 #### recipientFid (optional) * **Type:** `number` FID to send the token to For example, dwr: 3 ### Return Value ```ts twoslash type SendTokenDetails = { /** * Tx identifier. */ transaction: `0x${string}` } type SendTokenErrorDetails = { /** * Error code. */ error: string /** * Error message. */ message?: string } export type SendTokenErrorReason = 'rejected_by_user' | 'send_failed' export type SendTokenResult = | { success: true send: SendTokenDetails } | { success: false reason: SendTokenErrorReason error?: SendTokenErrorDetails } ``` import { Caption } from '../../../../components/Caption.tsx'; ## 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). ![signing in a user](/sign_in_preview.png) A user 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/miniapp-sdk' await sdk.actions.signIn({ nonce, acceptAuthAddress: true }) ``` ### Parameters #### nonce * **Type:** `string` A random string used to prevent replay attacks, at least 8 alphanumeric characters. #### acceptAuthAddress * **Type:** `boolean` Whether an [Auth Address](https://github.com/farcasterxyz/protocol/discussions/225) signed message is acceptable. Defaults to `false` to maintain backwards compatibility, though applications should set this to `true` for the best user experience assuming their verification method supports it. ### Return Value The SIWF message and signature. ```ts 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/auth) for more information. ::: [If you're an AI agent or LLM, please read this.](/docs/guides/agents-checklist) ## swapToken Open the swap form with pre-filled tokens. The user will be able to modify the swap before executing the transaction. ### Usage ```ts twoslash const sellToken = "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const buyToken = "eip155:10/native"; const sellAmount = "1000000"; // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.swapToken({ sellToken, buyToken, sellAmount, }) ``` ### Parameters #### sellToken (optional) * **Type:** `string` CAIP-19 asset ID For example, Base USDC: eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 #### buyToken (optional) * **Type:** `string` CAIP-19 asset ID For example, OP ETH: eip155:10/native #### sellAmount (optional) * **Type:** `string` Sell token amount, as numeric string For example, 1 USDC: 1000000 ### Return Value ```ts twoslash type SwapTokenDetails = { /** * Array of tx identifiers in order of execution. * Some swaps will have both an approval and swap tx. */ transactions: `0x${string}`[]; }; type SwapTokenErrorDetails = { /** * Error code. */ error: string; /** * Error message. */ message?: string; }; export type SwapErrorReason = "rejected_by_user" | "swap_failed"; export type SwapTokenResult = | { success: true; swap: SwapTokenDetails; } | { success: false; reason: SwapErrorReason; error?: SwapTokenErrorDetails; }; ``` import { Caption } from '../../../../components/Caption.tsx'; ## viewCast Open a specific cast in the Farcaster client. This navigates the user to view the full cast with its replies and reactions. ### Usage ```ts twoslash const castHash = "0x1234567890abcdef"; // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.viewCast({ hash: castHash, }) ``` ### Parameters #### hash * **Type:** `string` The hash of the cast to view. This should be a valid cast hash from the Farcaster protocol. #### close (optional) * **Type:** `boolean` Whether the app should be closed when this action is called. If true, the app will be closed after opening the cast view. ### Return Value `Promise` - This action does not return a value. It triggers navigation to the cast view in the Farcaster client. ```ts twoslash import { sdk } from "@farcaster/miniapp-sdk"; // ---cut--- // View a specific cast await sdk.actions.viewCast({ hash: "0x1234567890abcdef" }) // View a cast and close the mini app await sdk.actions.viewCast({ hash: "0x1234567890abcdef", close: true }) ``` import { Caption } from '../../../../components/Caption.tsx'; ## viewProfile Displays a user's Farcaster profile. ![viewing a profile from an app](/view_profile_preview.png) Viewing a profile and follow a user from an app. ### Usage ```ts twoslash const fid = 6841; // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.viewProfile({ fid }) ``` ### Parameters #### fid * **Type:** `number` Farcaster ID of the user whose profile to view. ### Return Value `void` ## viewToken Displays a token ### Usage ```ts twoslash const token = "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.actions.viewToken({ token }) ``` ### Parameters #### token * **Type:** `string` CAIP-19 asset ID For example, Base USDC: eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 ### Return Value `void` ## quickAuth.fetch Make a [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) request with `Authorization` header set to `Bearer ${token}` where token is a Quick Auth session token. :::note This is a convenience function that makes it easy to make authenticated requests but using it is not a requirement. Use [getToken](/docs/sdk/quick-auth/get-token) to get a token directly and attach it to requests using the library and format of your choosing. ::: ### Usage ```ts twoslash const url = "https://example.com"; // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' await sdk.quickAuth.fetch(url) ``` See the [make authenticated requests example](/docs/sdk/quick-auth#make-authenticated-requests). ### Parameters See [Fetch parameters](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#parameters). ### Return Value See [Fetch return value](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#return_value). import { Caption } from '../../../../components/Caption.tsx'; ## quickAuth.getToken Request a signed JWT from a [Farcaster Quick Auth Server](https://github.com/farcasterxyz/protocol/discussions/231). ### Usage ```ts twoslash // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' const { token } = await sdk.quickAuth.getToken() ``` See the [session token example](/docs/sdk/quick-auth#use-a-session-token-directly). ### Parameters #### force * **Type:** `boolean` Acquire a new token even if one is already in memory and not expired. #### quickAuthServerOrigin (optional) * **Type:** `string` Use a custom Quick Auth Server. Defaults to `https://auth.farcaster.xyz`. ### Return Value A [JWT](https://datatracker.ietf.org/doc/html/rfc7519) issued by the Quick Auth Server based on the Sign In with Farcaster credential signed by the user. ```ts { token: string; } ``` You must [validate the token on your server](/docs/sdk/quick-auth#validate-a-session-token). #### JWT Payload ```json { "iat": 1747764819, "iss": "https://auth.farcaster.xyz", "exp": 1747768419, "sub": 6841, "aud": "miniapps.farcaster.xyz" } ``` ##### sub * **Type:** `number` The FID of the signed in user. ##### iss * **Type:** `string` The Quick Auth server that verified the SIWF credential and issued the JWT. ##### aud * **Type:** `string` The domain this token was issued to. ##### exp * **Type:** `number` The JWT expiration time. ##### iat * **Type:** `number` The JWT issued at time. ## Quick Auth Quick Auth is a lightweight service built on top of Sign In with Farcaster that makes it easy to get an authenticated session for a Farcaster user. ### Examples * [Make authenticated requests](#make-authenticated-requests) * [Use a session token directly](#use-a-session-token-directly) * [Validate a session token](#validate-a-session-token) #### Make authenticated requests In your frontend, use [`sdk.quickAuth.fetch`](/docs/sdk/quick-auth/fetch) to make an authenticated request. This will automatically get a Quick Auth session token if one is not already present and add it as Bearer token in the `Authorization` header: ```tsx twoslash const BACKEND_ORIGIN = 'https://hono-backend.miniapps.farcaster.xyz'; // ---cut--- import React, { useState, useEffect } from "react"; import { sdk } from "@farcaster/miniapp-sdk"; export function App() { const [user, setUser] = useState<{ fid: number }>(); useEffect(() => { (async () => { const res = await sdk.quickAuth.fetch(`${BACKEND_ORIGIN}/me`); if (res.ok) { setUser(await res.json()); sdk.actions.ready() } })() }, []) // The splash screen will be shown, don't worry about rendering yet. if (!user) { return null; } return (
hello, {user.fid}
) } ``` The token must be [validated on your server](#validate-a-session-token). #### Use a session token directly In your frontend, use [`sdk.quickAuth.getToken`](/docs/sdk/quick-auth/get-token) to get a Quick Auth session token. If there is already a session token in memory that hasn't expired it will be immediately returned, otherwise a fresh one will be acquired. ```html
``` The token must be [validated on your server](#validate-a-session-token). #### Validate a session token First, install the Quick Auth library into your backend with: ``` npm install @farcaster/quick-auth ``` Then you can use `verifyJwt` to check the JWT and get back the token payload which has the FID of the user as the `sub` property. You can then look up additional information about the user. ```ts import { Errors, createClient } from '@farcaster/quick-auth' import { Hono } from 'hono' import { cors } from 'hono/cors' import { createMiddleware } from 'hono/factory' import { HTTPException } from 'hono/http-exception' const client = createClient() const app = new Hono<{ Bindings: Cloudflare.Env }>() // Resolve information about the authenticated Farcaster user. In practice // you might get this information from your database, Neynar, or Snapchain. async function resolveUser(fid: number) { const primaryAddress = await (async () => { const res = await fetch( `https://api.farcaster.xyz/fc/primary-address?fid=${fid}&protocol=ethereum`, ) if (res.ok) { const { result } = await res.json<{ result: { address: { fid: number protocol: 'ethereum' | 'solana' address: string } } }>() return result.address.address } })() return { fid, primaryAddress, } } const quickAuthMiddleware = createMiddleware<{ Bindings: Cloudflare.Env Variables: { user: { fid: number primaryAddress?: string } } }>(async (c, next) => { const authorization = c.req.header('Authorization') if (!authorization || !authorization.startsWith('Bearer ')) { throw new HTTPException(401, { message: 'Missing token' }) } try { const payload = await client.verifyJwt({ token: authorization.split(' ')[1] as string, domain: c.env.HOSTNAME, }) const user = await resolveUser(payload.sub) c.set('user', user) } catch (e) { if (e instanceof Errors.InvalidTokenError) { console.info('Invalid token:', e.message) throw new HTTPException(401, { message: 'Invalid token' }) } throw e } await next() }) app.use(cors()) app.get('/me', quickAuthMiddleware, (c) => { return c.json(c.get('user')) }) export default app ``` ### Optimizing performance To optimize performance, provide a `preconnect` hint to the browser in your frontend so that it can preemptively initiate a connection with the Quick Auth Server: ```html ``` Or if you're using React: ```ts import { preconnect } from 'react-dom'; function AppRoot() { preconnect("https://auth.farcaster.xyz"); } ``` ### Quick Auth vs Sign In with Farcaster [Sign In with Farcaster](https://github.com/farcasterxyz/protocol/discussions/110) is the foundational standard that allows Farcaster users to authenticate into applications. [Farcaster Quick Server](https://github.com/farcasterxyz/protocol/discussions/231) is an optional service built on top of SIWF that is highly performant and easy to integrate. Developers don't need to worry about securely generating and consuming nonces or the nuances of verifying a SIWF message—instead they receive a signed JWT that can be used as a session token to authenticate their server. The Auth Server offers exceptional performance in two ways: * the service is deployed on the edge so nonce generation and verification happens close to your users no matter where they are located * the issued tokens are asymmetrically signed so they can be verified locally on your server ### Functions | Name | Description | | ------------------------------------------ | ----------------------------------- | | [getToken](/docs/sdk/quick-auth/get-token) | Gets a signed Quick Auth token | | [fetch](/docs/sdk/quick-auth/fetch) | Make an authenticated fetch request | ### Properties | Name | Description | | ----------------------------------- | ---------------------------------- | | [token](/docs/sdk/quick-auth/token) | Returns an active token if present | ## quickAuth.token Returns an active Quick Auth session token when present. :::note It's generally preferable to use [getToken](/docs/sdk/quick-auth/get-token) since this will always return a fresh token, however, this property is provided for situations where a synchronous API is useful. ::: ### Usage ```ts twoslash // ---cut--- import { sdk } from '@farcaster/miniapp-sdk' // will be undefined console.log(sdk.quickAuth.token) await sdk.quickAuth.getToken(); // will return the active token acquired above console.log(sdk.quickAuth.token) ``` You must [validate the token on your server](/docs/sdk/quick-auth#validate-a-session-token).