Skip to content
Notion Arena

Registry

Not a gallery. A manifest.

Every component is published in the shadcn/ui registry-item schema, with its source inlined rather than linked. A CLI, an MCP server, an agent or a 20-line script can list the catalog, read a component's exact capabilities, and install it without anyone opening a browser.

203 items192 with inlined HTML11 with generated API payloadsCORS: *no auth to read

Try it live

This panel is not a mock. It fetches /r/index.json from your browser and then each item as you click it, the same two requests an agent would make.

GET /r/pomodoro-timer.json

Loading index…

0 items · schema /r/schema.jsonapplication/json · Access-Control-Allow-Origin: *

Endpoints

Reads are open and cacheable. The only endpoint that touches your workspace is the install one, and its plan mode deliberately requires no credentials at all.

MethodPathReturnsAuth
GET/r/index.jsonThe whole catalog in the shadcn registry.json envelopenone
GET/r/{name}.jsonOne item, with its HTML / recipe JSON / manual steps inlinednone
GET/r/schema.jsonJSON Schema for every widget's configuration parametersnone
GET/embed/{slug}The configured widget as a framable HTML documentnone
GET/api/widget/{slug}The same HTML with config baked in, as a file downloadnone
GET/widgets/{slug}.htmlRaw source, exactly as it is committednone
POST/api/installPlan or execute an install. mode: plan needs no credentialstoken for writes

Consuming it

Three ways in, from least to most code.

A

Point an agent at the registry

Use the official Notion Arena MCP server or Agent Skill to give Claude Code, Cursor, or Claude Desktop tools to browse, configure, and install components directly.

CLI / skills.shbash
# Add MCP server to Claude Code
claude mcp add --scope project notion-arena -- npx -y notion-arena-mcp

# Or run standalone via npx
npx -y notion-arena-mcp@0.2.2

# Install Agent Skill (cross-agent via skills.sh)
npx skills add SynergyFlowLabs/notion-arena --skill notion-arena
or MCP config in claude_desktop_config.json / .cursor/mcp.json
{
  "mcpServers": {
    "notion-arena": {
      "command": "npx",
      "args": ["-y", "notion-arena-mcp@0.2.2"],
      "env": {
        "NOTION_ARENA_SITE_URL": "https://notionarena.vercel.app"
      }
    }
  }
}
B

Two fetches and a file write

The index gives you names and capabilities. The item gives you the bytes. That is the whole protocol: there is no SDK and no auth for reads.

install.tsts
// Node 20+: no dependencies
const registry = "https://notionarena.vercel.app/r";

const { items } = await (await fetch(`${registry}/index.json`)).json();

// pick the interactive, offline-safe widgets
const candidates = items.filter(
  (i) => i.meta.kind === "widget" && i.meta.capabilities.offlineSafe
);

for (const item of candidates) {
  const full = await (await fetch(`${registry}/${item.name}.json`)).json();
  const html = full.files.find((f) => f.path.endsWith(".html")).content;

  // write it somewhere your installer can read
  await Bun.write(`./notion/widgets/${item.name}.html`, html);
}
C

Or let the server do the Notion calls

Plan mode returns the exact request sequence with bodies. Switch to api mode with a token and a parent page id and it executes them, including the two-pass data-source resolution relations require.

shellbash
# 1 · see the plan: no credentials, no side effects
curl -s -X POST https://notionarena.vercel.app/api/install \
  -H 'content-type: application/json' \
  -d '{"component":"crm-pipeline","mode":"plan"}' | jq '.plan.calls[] | .method + " " + .path'

# 2 · run it
curl -s -X POST https://notionarena.vercel.app/api/install \
  -H 'content-type: application/json' \
  -d '{
    "component": "crm-pipeline",
    "mode": "api",
    "token": "'"$NOTION_TOKEN"'",
    "parentPageId": "'"$PAGE_ID"'"
  }'
and the widget equivalent
# a widget, installed as a native HTML block
curl -s -X POST https://notionarena.vercel.app/api/install \
  -H 'content-type: application/json' \
  -d '{
    "component": "pomodoro-timer",
    "mode": "html-block",
    "token": "'"$NOTION_TOKEN"'",
    "parentPageId": "'"$PAGE_ID"'",
    "config": { "work": 50, "short": 10, "accent": "#0f7b6c" }
  }'

The item schema

The envelope is shadcn's, because tooling already understands it. The meta object is ours, and it carries the two things a consumer actually needs to decide what to do: which channels apply, and what the component is capable of.

namestringThe slug. Stable, lowercase, kebab-case: the address you install by.
typeregistry:component | registry:blockcomponent for widgets (a file you place), block for recipes (a structure you create).
title / descriptionstringHuman-facing name and the one-line pitch shown in the catalog.
categoriesstring[]Which of the eight catalog categories it belongs to.
tagsstring[]kind, tier and the component's own tags. Filterable, and what search matches against.
meta.kindwidget | nativeIt tells you which delivery channels apply.
meta.tierfree | proWhether it is in the free set.
meta.version / updatedAtsemver / dateFor deciding whether to re-install. Downloaded HTML blocks are frozen; embed URLs are not.
meta.deliverystring[]The channels this component actually supports, never guessed from kind.
meta.capabilitiesobjectinteractive, offlineSafe, persistsLocally, writesToNotion, needsNetwork. Booleans, not adjectives.
meta.paramsWidgetParam[]Widgets only: every configuration key with type, bounds, options, default and help text.
meta.embedUrl / downloadUrlstringWidgets only: the two URLs a consumer needs, already absolute.
meta.recipe / installUrlstringRecipes only: the recipe id and the endpoint that installs it.
files[]object[]path, type, target and (on /r/{name}.json) content. The HTML is inlined, not linked.

Versioning

Items carry semver in meta.version. Widget source files are frozen per version at /widgets/{slug}.html; embed URLs always serve the latest patch. If you are shipping a template to a customer, download the file: that is the whole point of the second channel.

Stability

Slugs never change once published. A slug that is retired keeps resolving with a meta.deprecated flag rather than 404ing, so a manifest pinned in someone's config does not silently break.

Self-hosting

Widgets are single files with no dependencies. Fork the repo, serve public/widgets/ from anywhere, and point your registry base at your own host. Nothing phones home: there is nothing to phone.

Caching

Registry JSON is cached 5 minutes at the edge with stale-while-revalidate. Widget HTML is cached 60 seconds, so a configuration change is visible almost immediately while repeat loads stay cheap.

Everything published

203 items. Click a name for the human page, or the code for the manifest.

🧑‍🤝‍🧑Org Chartorg-chart.jsonhtml