ShotOG
February 12, 2026 · View on GitHub
Beautiful OG images. One API call.
Generate stunning Open Graph images for your website with a single URL — no design tools, no headless browsers, no infrastructure to manage.
Why ShotOG?
Every page you share on Twitter, Slack, or Discord needs an OG image. You can:
Design each one manually in Figma(doesn't scale)Run a headless browser on your server(slow, expensive, breaks)- Call ShotOG's API — one URL, beautiful image, ~50ms at the edge
ShotOG runs on Cloudflare Workers. No cold starts. No servers. Just fast images.
Quick Start
Option 1: Just use a URL
Drop this into your HTML <head>:
<meta property="og:image" content="https://shotog.2214962083.workers.dev/v1/og?title=My%20Page%20Title&template=blog&author=John" />
That's it. No signup required for up to 10 images/day.
Option 2: Use the TypeScript SDK
npm install shotog
import { ShotOG } from "shotog";
const og = new ShotOG({ apiKey: "sk_..." }); // optional, increases rate limit
// Generate a URL (no network request)
const imageUrl = og.url({
title: "How We Scaled to 1M Users",
subtitle: "A deep dive into our infrastructure",
template: "blog",
author: "Jane Smith",
});
// → https://shotog.2214962083.workers.dev/v1/og?title=How+We+Scaled...
// Or fetch the image binary directly
const imageBuffer = await og.generate({
title: "Product Launch",
template: "announcement",
});
Option 3: cURL
# GET — returns PNG directly
curl "https://shotog.2214962083.workers.dev/v1/og?title=Hello&template=basic" -o og.png
# POST — JSON body, more options
curl -X POST https://shotog.2214962083.workers.dev/v1/og \
-H "Content-Type: application/json" \
-d '{"title":"Hello World","template":"product","subtitle":"Built with ShotOG"}' \
-o og.png
Templates
8 professionally designed templates for every use case:
| Template | Best For | Preview |
|---|---|---|
basic | General pages, social sharing | |
blog | Blog posts, articles | |
product | SaaS products, launches | |
social | Social media posts | |
event | Events, webinars | |
changelog | Release notes | |
testimonial | Customer quotes | |
announcement | Major updates, launches |
API Reference
Generate OG Image
GET /v1/og?title=...&template=...
POST /v1/og (JSON body)
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Main heading text |
template | string | No | Template name (default: basic) |
subtitle | string | No | Secondary text |
eyebrow | string | No | Small text above title (category, label) |
author | string | No | Author name |
avatar | string | No | Avatar image URL (shown in blog/social/testimonial) |
logo | string | No | Logo image URL (shown in basic/product) |
fontUrl | string | No | URL to TTF/OTF font file (max 5MB, cached 1h) |
domain | string | No | Domain watermark |
bgColor | string | No | Background color (hex, e.g. 1a1a2e) |
textColor | string | No | Text color (hex) |
accentColor | string | No | Accent color (hex) |
format | string | No | png (default) or svg |
width | number | No | Image width 200-2400 (default: 1200) |
height | number | No | Image height 200-1260 (default: 630) |
api_key | string | No | API key for higher limits |
Batch Generate (up to 20 images)
curl -X POST https://shotog.2214962083.workers.dev/v1/og/batch \
-H "Content-Type: application/json" \
-H "X-Api-Key: sk_..." \
-d '{
"images": [
{"id": "hero", "title": "My Product", "template": "product"},
{"id": "blog-1", "title": "First Post", "template": "blog", "author": "Alice"},
{"id": "blog-2", "title": "Second Post", "template": "blog", "author": "Bob"}
],
"defaults": {"format": "png", "width": 1200, "domain": "example.com"}
}'
Response:
{
"results": [
{"id": "hero", "success": true, "dataUri": "data:image/png;base64,..."},
{"id": "blog-1", "success": true, "dataUri": "data:image/png;base64,..."},
{"id": "blog-2", "success": true, "dataUri": "data:image/png;base64,..."}
],
"summary": {"total": 3, "succeeded": 3, "failed": 0}
}
Batch features:
- Max 20 images per request
defaultsobject applies to all images (individual params override)- Parallel rendering via
Promise.allSettled - Quota pre-check: if insufficient quota, returns 429 before rendering
- Only successful renders count toward usage
Get API Key (Self-Service)
curl -X POST https://shotog.2214962083.workers.dev/v1/keys \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
Returns:
{
"id": "key_abc123",
"key": "sk_live_...",
"tier": "free",
"monthly_limit": 500,
"message": "Store your API key safely — it cannot be retrieved later."
}
Check Usage
curl https://shotog.2214962083.workers.dev/v1/keys/usage \
-H "X-Api-Key: sk_live_..."
List Templates
curl https://shotog.2214962083.workers.dev/v1/og/templates
SDK
The TypeScript/JavaScript SDK wraps all API functionality:
import { ShotOG } from "shotog";
// Initialize
const og = new ShotOG({ apiKey: "sk_..." });
// Build image URL (no network call)
og.url({ title: "My Post", template: "blog" });
// Generate image binary
await og.generate({ title: "My Post", template: "blog" });
// List templates
await og.templates();
// Check usage
await og.usage();
// Create a new API key (static method)
await ShotOG.createKey("email@example.com");
Framework Examples
Next.js
// app/layout.tsx
export function generateMetadata({ params }) {
return {
openGraph: {
images: [`https://shotog.2214962083.workers.dev/v1/og?title=${encodeURIComponent(params.title)}&template=blog`],
},
};
}
Astro
---
const ogImage = `https://shotog.2214962083.workers.dev/v1/og?title=${encodeURIComponent(title)}&template=product`;
---
<meta property="og:image" content={ogImage} />
Hugo
{{ $ogImage := printf "https://shotog.2214962083.workers.dev/v1/og?title=%s&template=blog&author=%s" (querify .Title) (querify .Params.author) }}
<meta property="og:image" content="{{ $ogImage }}" />
Tech Stack
- Runtime: Cloudflare Workers (edge, ~17ms cold start)
- Framework: Hono (lightweight, fast)
- Rendering: @cf-wasm/satori (JSX → SVG at the edge)
- Rasterizer: @cf-wasm/resvg (SVG → PNG)
- Database: Cloudflare D1 (API keys + usage tracking)
- Caching: Built-in CDN cache headers
Self-Hosting
ShotOG is MIT licensed. Deploy your own instance:
git clone https://github.com/nicepkg/shotog.git
cd shotog
npm install
# Set up D1 database
npx wrangler d1 create shotog-prod
# Update wrangler.toml with your database_id
npx wrangler d1 execute shotog-prod --file=./migrations/001_init.sql
# Deploy
npx wrangler deploy
Pricing
| Plan | Price | Monthly Images | Rate Limit |
|---|---|---|---|
| Demo | Free | 10/day | 10/day |
| Free | Free | 500/month | 60/min |
| Starter | $9/mo | 5,000/month | 120/min |
| Pro | $29/mo | 25,000/month | 300/min |
| Scale | $79/mo | 100,000/month | 600/min |