Skip to content

Embeds#

View Markdown

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" />
ubugeeei-prod/ox-content

all-in-one markdown toolchain ― fastest, tiniest, framework agnostic, powerful, customizable

Rust17711

A source snippet pinned to a ref and line range:

<GitHub repo="ubugeeei-prod/ox-content" path="README.md" ref="main" loc="1-10" />
L1-L10 · 10 LOC
<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="./assets/oxcontent-light.svg">
    <source media="(prefers-color-scheme: light)" srcset="./assets/oxcontent-dark.svg">
    <img alt="Ox Content logo" src="./assets/oxcontent-dark.svg" height="60">
  </picture>
</p>

<p align="center">
  <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" />
Vite
Next Generation Frontend Tooling
vitejs
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>
vp install -D @ox-content/vite-plugin
pnpm add -D @ox-content/vite-plugin
bun add -D @ox-content/vite-plugin
npm install -D @ox-content/vite-plugin
yarn add -D @ox-content/vite-plugin

The 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>
brew install oxc
apt install oxc
winget install oxc

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" />
jack@jack
just setting up my twttr
18.0K replies308.2K likes

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>

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>
Demoindex.htmlBoots on interaction
npm install
npm run dev
Entry index.html2 commandsStatic source bundleRequires cross-origin isolation

See WebContainer Embed for the isolation requirements.

Last updated: