1. Get a key
Grab a free key from the homepage and add it to .env:
OGSMITH_KEY_ID=ab12cd34
OGSMITH_SIGNING_SECRET=…
2. Add a helper
Create src/lib/og.js (build-time only — the secret is never shipped to the browser):
import { createHmac } from "node:crypto";
const KID = import.meta.env.OGSMITH_KEY_ID; // from POST /v1/keys
const SECRET = import.meta.env.OGSMITH_SIGNING_SECRET;
// Strict RFC 3986 encoding — encodeURIComponent plus the five chars it skips.
const enc = (s) =>
encodeURIComponent(s).replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
export function ogImage(params) {
const p = { template: "article", ...params, kid: KID };
const canonical = Object.keys(p).sort().map((k) => enc(k) + "=" + enc(p[k])).join("&");
const sig = createHmac("sha256", SECRET).update(canonical).digest("hex");
return "https://api.ogsmith.dev/v1/og?" + new URLSearchParams({ ...p, sig });
}
3. Use it in your layout
---
// src/layouts/BlogPost.astro
import { ogImage } from "../lib/og.js";
const { frontmatter } = Astro.props;
const og = ogImage({ title: frontmatter.title, description: frontmatter.description, site: "yoursite.dev" });
---
<head>
<meta property="og:title" content={frontmatter.title} />
<meta property="og:image" content={og} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={og} />
</head>
Because the URL is computed at build time, a fully static Astro site gets dynamic-looking OG images with zero runtime dependencies.
Frequently asked questions
Does this work with SSR (Astro on Node/Vercel/Netlify adapters)?
Yes, identically — the helper runs wherever the page renders. For content collections, call ogImage() with data from each entry.
Why signed URLs instead of an API key?
The og:image URL ends up in your public HTML, where anyone can read it. A signed URL exposes no secret: the HMAC signature covers every parameter, so changing the title (or anything else) invalidates it. A leaked signed URL can only ever render that exact image.
Does every page view burn my quota?
No. Images are served through CloudFront with immutable caching, and cache hits don't count against your quota. A given og:image URL renders once; every crawler and preview after that is a free edge hit.
Templates available: basic, gradient, split, article, minimal, code — try them in the playground. Full parameter list in the docs.