Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,9 @@
"group": "Technical Guides",
"pages": [
"mini-apps/technical-guides/sign-manifest",
"mini-apps/technical-guides/dynamic-embeds",
"mini-apps/technical-guides/neynar-notifications"

]
},
{
Expand Down
200 changes: 200 additions & 0 deletions docs/mini-apps/technical-guides/dynamic-embeds.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
title: Generate Dynamic Embed Images
description: Create viral loops by turning every user interaction into dynamic, shareable content directly in the feed.
---

Embeds are the first thing users see when they encounter your mini app in their feed. Each share can display unique, contextual content tailored to drive engagement.

<Panel>
<Frame caption="How metadata transforms into embeds">
<img src="/images/minikit/Diagram.png" alt="Diagram showing the flow from mini app URL to metadata reading to image generation and final embed rendering in the Base app" />
</Frame>
When users share your mini app `URL`, the Base app requests your page, reads the fc:miniapp metadata, and fetches the `imageUrl`. You can serve either a static file (same image for everyone) or a dynamic endpoint that generates unique images on-demand based on URL parameters.
</Panel>

<Note>
This guide uses Minikit but the principles apply to any framework with server-side rendering.
</Note>

## Implementation

This guide shows how to create shareable links with dynamic embed images. Users click a share button, which opens a compose window with their personalized link. When shared, the embed displays a unique image with their username.

Copy link
Contributor

@hughescoin hughescoin Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just turn this into a step:

<Step title="Install Vercel Open Graph Library">

<Note>
Skip this step if your app uses Next.js App Router, as the package is already included
</Note>
npm install @vercel/og

</Step>

<Steps>
<Step title="Install the required package">
Install `@vercel/og` by running the following command inside your project directory. This isn't required for Next.js App Router projects, as the package is already included:

```bash
npm install @vercel/og
```
</Step>
<Step title="Create the image generation API endpoint">
Build an API route that generates images based on the username parameter.

```tsx lines expandable wrap app/api/og/[username]/route.tsx
import { ImageResponse } from "next/og";

export const dynamic = "force-dynamic";

export async function GET(
request: Request,
{ params }: { params: Promise<{ username: string }> }
) {
const { username } = await params;

return new ImageResponse(
(
<div
style={{
backgroundColor: 'black',
backgroundSize: '150px 150px',
height: '100%',
width: '100%',
display: 'flex',
color: 'white',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flexWrap: 'nowrap',
}}
>
Hello {username}
</div>
),
{
width: 1200,
height: 630,
}
);
}
```

This endpoint generates a unique image for each username: `/api/og/alice`, `/api/og/bob`, etc.

<Warning>
`<div>` elements must have `display: "flex"` or `display: "none"`. If you see a 500 error when accessing `/share/[username]`, check your ImageResponse JSX structure.
</Warning>
</Step>

<Step title="Create shareable page with dynamic metadata">
Build a page route that uses the username to generate `fc:miniapp` metadata pointing to your image endpoint.

```tsx lines expandable wrap app/share/[username]/page.tsx
import { minikitConfig } from "../../../minikit.config";
import { Metadata } from "next";

export async function generateMetadata(
{ params }: { params: Promise<{ username: string }> }
): Promise<Metadata> {
try {
const { username } = await params;

return {
title: minikitConfig.miniapp.name,
description: minikitConfig.miniapp.description,
other: {
"fc:miniapp": JSON.stringify({
version: minikitConfig.miniapp.version,
imageUrl: `${minikitConfig.miniapp.homeUrl}/api/og/${username}`,
button: {
title: `Join the ${minikitConfig.miniapp.name} Waitlist`,
action: {
name: `Launch ${minikitConfig.miniapp.name}`,
type: "launch_frame",
url: `${minikitConfig.miniapp.homeUrl}`,
},
},
}),
},
};
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'error',
message: 'Failed to generate metadata',
error: errorMessage
}));

return {
title: minikitConfig.miniapp.name,
description: minikitConfig.miniapp.description,
};
}
}

export default async function SharePage(
{ params }: { params: Promise<{ username: string }> }
) {
const { username } = await params;

return (
<div>
<h1>Share Page - {username}</h1>
</div>
);
}
```
When someone visits `/share/alice`, the metadata points to `/api/og/alice` for the embed image.
</Step>

<Step title="Add share button with composeCast">
Create a button that opens Farcaster's compose window with the user's personalized share link.

```tsx lines expandable wrap app/page.tsx highlight={6, 9-15}
import { useMiniKit, useComposeCast } from "@coinbase/onchainkit/minikit";
import { minikitConfig } from "./minikit.config";

export default function HomePage() {
const { context } = useMiniKit();
const { composeCast } = useComposeCast();


const handleShareApp = () => {
const userName = context?.user?.displayName || 'anonymous';
composeCast({
text: `Check out ${minikitConfig.miniapp.name}!`,
embeds: [`${window.location.origin}/share/${userName}`]
});
};

return (
<div>
<button onClick={handleShareApp}>
Share Mini App
</button>
</div>
);
}
```

When you click the button, it opens the compose window with `/share/alice` as the embed. The embed displays the dynamic image from `/api/og/alice`.
</Step>

<Step title="Test the flow">
Verify the complete sharing flow works.

```bash lines wrap
# Start your app
npm run dev

# Test the image endpoint directly
curl http://localhost:3000/api/og/testuser > test.png
open test.png

# Visit the share page to verify metadata
curl http://localhost:3000/share/testuser | grep "fc:miniapp"
```

Click the share button in your app to test the full experience. You should see the compose window open with your personalized share link, and the embed should display your custom generated image.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's replace "Verify the complete sharing flow works" with this paragraph.

</Step>
</Steps>

## Related Concepts

<CardGroup cols={1}>
<Card title="Troubleshooting" href="/mini-apps/troubleshooting/how-search-works">
Troubleshooting tips for embeds not displaying.
</Card>
</CardGroup>
Loading