Embeds
Embeds are HTML-like tags in Markdown that expand into static HTML at transform time. Two are enabled by default because they produce plain static markup; everything else is opt-in.
| Embed | Option | Default | Authoring form |
|---|---|---|---|
| GitHub card | embeds.github |
true |
<GitHub repo="owner/name" /> |
| Open Graph link card | embeds.openGraph |
true |
<OgCard url="https://..." /> |
| Package manager tabs | embeds.pm |
false |
<pm>npm install pkg</pm> |
| Twitter/X | embeds.twitter |
false |
<Tweet /> or <XPost /> |
| Bluesky | embeds.bluesky |
false |
<Bluesky /> |
| Spotify | embeds.spotify |
false |
<Spotify url="https://..." /> |
| StackBlitz | embeds.stackBlitz |
false |
<StackBlitz url="https://..." /> |
| WebContainer | embeds.webContainer |
false |
<WebContainer /> |
Tabs and YouTube embeds are not part of the embeds option: they are always
processed in SSG builds and dev preview, with no configuration needed. They are
covered below because they share the same authoring model.
Disable every built-in embed with embeds: false, or configure embeds
individually:
import { oxContent } from "@ox-content/vite-plugin";
export default {
plugins: [
oxContent({
embeds: {
github: { maxSourceLines: 120 },
openGraph: { timeout: 5000 },
pm: { sync: true },
twitter: true,
bluesky: true,
},
}),
],
};
GitHub Cards
embeds.github renders repository cards and source snippets from the GitHub
API at build time. The output is static HTML — no client-side JavaScript, no
third-party widget script.
A repository card:
<GitHub repo="ubugeeei-prod/ox-content" />
A framework agnostic fastest and beautiful documentation & markdown tooling
A source snippet pinned to a ref and line range:
<GitHub repo="ubugeeei-prod/ox-content" path="README.md" ref="main" loc="1-10" />
1<p align="center">2 <picture>3 <source media="(prefers-color-scheme: dark)" srcset="./assets/oxcontent-light.svg">4 <source media="(prefers-color-scheme: light)" srcset="./assets/oxcontent-dark.svg">5 <img alt="Ox Content logo" src="./assets/oxcontent-dark.svg" height="60">6 </picture>7</p>8 9<p align="center">10 <strong>High-performance Markdown toolkit</strong><br>A permalink form is also supported — paste a GitHub blob URL with #L2-L8
line anchors:
<GitHub permalink="https://github.com/owner/repo/blob/abc123/src/index.ts#L2-L8" />
| Option | Default | Purpose |
|---|---|---|
token |
"" |
GitHub API token for rate limits and private repos. |
cache |
true |
Cache API responses in memory. |
cacheTTL |
3600000 |
Cache lifetime in milliseconds. |
maxSourceBytes |
200000 |
Skip files larger than this. |
maxSourceLines |
120 |
Max inline lines when no range is given. |
process.env.GITHUB_TOKEN is picked up automatically when no explicit token
is configured. If a repository or file cannot be fetched during the build —
offline CI, rate limits, an invalid path — the embed renders a fallback link
card instead of failing the build.
Open Graph Cards
embeds.openGraph fetches a page's Open Graph metadata at build time and
renders a static link card:
<OgCard url="https://vite.dev" />
| Option | Default | Purpose |
|---|---|---|
timeout |
10000 |
Fetch timeout in milliseconds. |
cache |
true |
Cache fetched metadata. |
cacheTTL |
3600000 |
Cache lifetime in milliseconds. |
userAgent |
ox-content-ogp-bot/1.0 ... |
User agent sent to the target. |
Unreachable pages fall back to a plain link card. Requests to localhost, private IP ranges, and non-HTTP(S) schemes are rejected, so Markdown content cannot probe the network the build runs in.
Package Manager Tabs
embeds.pm expands one npm-style command into an accessible tab group for
npm, pnpm, yarn, bun, and vp (Vite+):
oxContent({
embeds: {
pm: true,
},
});
<pm>npm install -D @ox-content/vite-plugin</pm>
npm install -D @ox-content/vite-pluginpnpm add -D @ox-content/vite-pluginyarn add -D @ox-content/vite-pluginbun add -D @ox-content/vite-pluginvp install -D @ox-content/vite-pluginThe command is converted natively in Rust — npm install -D becomes
pnpm add -D, yarn add -D, bun add -D, and vp install -D, while
npx <bin> becomes vp exec -- <bin>. The tabs work without client-side
JavaScript; selection uses CSS :has(). Opt in to pm: { sync: true } to
synchronize the selected package manager across every block on the page via
localStorage. See
Package Manager Tabs for the full
conversion table.
Tabs
Generic tab groups use the same widget as package-manager tabs and are always available in SSG builds and dev preview:
<tabs>
<tab label="macOS">brew install oxc</tab>
<tab label="Linux">apt install oxc</tab>
<tab label="Windows">winget install oxc</tab>
</tabs>
A <tab> without a label attribute falls back to Tab 1, Tab 2, and so
on.
YouTube
YouTube embeds are always processed in SSG builds and dev preview. The iframe
uses privacy-enhanced mode (youtube-nocookie.com) and lazy loading by
default:
<youtube id="aqz-KE-bpKQ" title="Big Buck Bunny" />
id, url, and href attributes are accepted; youtu.be, watch?v=,
shorts, and embed URL shapes are all recognized.
Twitter/X
embeds.twitter renders posts as static cards and never loads the third-party
widget script. With twitter: true, the embed is a privacy-conscious link
card:
<XPost url="https://x.com/jack/status/20" />

Use the object form to fetch the post body, author, avatar, and photos at build time and serve them from your own origin:
oxContent({
embeds: {
twitter: {
fetch: true,
lang: "en",
mediaOutputDir: "public/ox-content/twitter",
mediaPublicPath: "/ox-content/twitter",
},
},
});
| Option | Default | Purpose |
|---|---|---|
fetch |
false |
Fetch post content at build time. |
lang |
"en" |
Syndication language and displayed date. |
timeout |
10000 |
Metadata request timeout in milliseconds. |
cache |
true |
In-memory and persistent JSON caches. |
cacheDir |
.cache/ox-content/twitter |
Persistent metadata cache directory. |
mediaOutputDir |
public/ox-content/twitter |
Local directory for avatars and photos. |
mediaPublicPath |
/ox-content/twitter |
URL prefix emitted for downloaded media. |
Downloaded media is served from your site, so a strict img-src 'self' CSP
keeps working. Deleted or private posts fall back to the link-only card
instead of failing the build. See
Twitter/X Embed for details.
Bluesky
embeds.bluesky renders a static card. The element body provides the text
shown in the card, so no network request is needed at all:
<Bluesky url="https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l">
👋 Bluesky is an open social network
</Bluesky>
👋 Bluesky is an open social network
Spotify
embeds.spotify renders the official iframe player for tracks, albums,
playlists, episodes, shows, and artists:
<Spotify url="https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC" />
The output is an <iframe> pointing at open.spotify.com/embed/... with lazy
loading. Unlike the static cards above it is a real third-party player, which
is why it stays opt-in.
StackBlitz
embeds.stackBlitz turns a StackBlitz project URL into a sandboxed iframe
with embed=1 appended:
<StackBlitz url="https://stackblitz.com/edit/vitejs-vite-abc123"></StackBlitz>
WebContainer
embeds.webContainer emits a lazy placeholder carrying the project source and
cross-origin isolation metadata, for sites that boot
WebContainers on interaction. The placeholder
itself is fully static:
<WebContainer entry="index.html" title="Demo">
npm install
npm run dev
</WebContainer>
npm install
npm run devSee WebContainer Embed for the isolation requirements.
Related
- Mermaid Diagrams — diagram fences rendered to static SVG.
- Built-in Features overview