AuraImage OG Images
An OG template is a design a project stores once and renders on demand into a social preview image. You author it as an HTML file in the repo, push it with the CLI or the push_og_template MCP tool, and point og:image at its Render URL. AuraImage does the rendering and the caching; the app never runs a renderer and never bundles a font.
When to Apply
- A page needs a social preview image, or an existing
og:imageis a static file - Writing Next.js
generateMetadata, a<meta property="og:image">tag, or any link-preview markup - The project already uses
next/og,@vercel/og, or Satori inside a route handler - A task mentions OG images, social cards, Twitter cards, share images, or link previews
The Shape of the Thing
og/blog-post.html the design, in the repo, with {{name}} holes
aura og push blog-post ./og/blog-post.html
→ https://cdn.auraimage.ai/v1/og/my-app/blog-post?title=Hello
A template's canvas, fonts, defaults, and quality are flags on the push, never markup in the file. Keep them in an npm script so the repo remembers them:
{
"scripts": {
"og:push": "aura og push blog-post ./og/blog-post.html --font Inter"
}
}
Start From a Starter
Four ready-to-push templates ship with this skill in starters/. Copy one into the project's og/ directory and edit the copy — do not write a card from scratch on the first try.
| Starter | Canvas | Variables |
|---|---|---|
blog-post.html |
1200x630 | kicker title author cover (image) |
product-launch.html |
1200x630 | product tagline hero (image) |
docs-page.html |
1200x630 | path section title |
square-card.html |
1080x1080 | title handle |
All four declare no fonts and use the built-in family, so they push with no --font and render with no network fetch.
The Canvas column is a flag, not a note. preview and push both default to 1200x630, and nothing rejects a design pushed at the wrong size, so pass the starter's canvas to --width and --height on both commands. square-card is the one that differs from the default:
aura og preview ./og/square-card.html --width 1080 --height 1080 --var title="A real title" --var handle="@you"
aura og push square-card ./og/square-card.html --width 1080 --height 1080
Rules
Read the rule file before doing the matching work. Each one is short.
rules/authoring.md what the markup may and may not contain
rules/pushing.md the push, the flags, and reading a rejection
rules/render-urls.md the URL grammar and wiring it into a page
Authoring (CRITICAL)
og-tailwind-in-tw— Tailwind utilities go in atwattribute, neverclass. Aclasstoken with no matching<style>rule is a400at push. This is the single most common mistake; a card that silently loses its styling is the worst outcome, so push refuses it.og-variables-are-text—{{name}}holes are plain text, HTML-escaped, capped at 500 characters. A variable may not appear in atwattribute, astyleattribute, a<style>block, or inside inline<svg>. Push rejects each of those by name.og-images-same-project— Every image, static or through a slot, is a project-relative serve path (w=1200/blog/hero). Absolute URLs,data:URIs, and CSSurl()are rejected. Upload the image to the project first.
Pushing (HIGH)
og-metadata-in-flags— Canvas, fonts, defaults, and quality are--width,--height,--font,--default,--quality. Never invent a metadata comment or front-matter block in the HTML file.og-preview-before-push— Runaura og preview ./og/<name>.html --var title="…"and look at the PNG before pushing. It renders with the same engine.og-push-is-idempotent— Re-pushing an unchanged file is a no-op with the same version. Re-pushing a changed file refreshes every Render URL within about a minute, with no cache-buster.
Render URLs (HIGH)
og-no-extension-means-png— A Render URL with no extension is PNG, deterministically..jpgand.webppin those formats..avifis a400.og-size-is-not-in-the-url— A template's canvas is fixed. A card at another size is another template.og-every-variable-or-a-default— A hole with no value and no declared default is a400naming it, never a blank card. Declare a default at push, or always pass the value.
Wiring It Into a Page
Next.js App Router:
import { buildOgUrl } from '@auraimage/sdk';
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug);
return {
openGraph: {
images: [
{
url: buildOgUrl({
cdnUrl: process.env.NEXT_PUBLIC_AURA_CDN_URL!,
project: process.env.NEXT_PUBLIC_AURA_PROJECT_NAME!,
template: 'blog-post',
vars: { kicker: post.section, title: post.title, author: post.author, cover: `w=768/blog/${post.slug}` }
}),
width: 1200,
height: 630
}
]
}
};
}
Anything else, including plain HTML: build the URL as a string. Variables sort by key, values are URL-encoded, and only v is reserved.
<meta property="og:image" content="https://cdn.auraimage.ai/v1/og/my-app/blog-post?title=Hello%20world" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
Agent Workflow
- Check for an existing template:
aura og list. - Copy the closest starter into
og/<name>.htmland edit it. Carry its canvas into--widthand--heighton every command below. - Preview:
aura og preview ./og/<name>.html --var title="A real title"and look at the file. - Push:
aura og push <name> ./og/<name>.html, or thepush_og_templateMCP tool. - Record the push flags in an
og:pushnpm script. - Wire
og:imageto the Render URL the push printed.
A push that fails prints the exact offender. Fix that one thing and push again; do not restructure the template around a guess.
Credentials
AURA_SECRET_KEY and AURA_PROJECT in .env.local. aura init prints both. The Secret Key is server-only and must never be written under a public-exposure prefix such as NEXT_PUBLIC_. Delegate credential setup to the auraimage-api-key skill.