# Storyblok

> Storyblok CMS engineering — component schemas, the Delivery vs Management API split, taxonomy and filter_query, draft/preview wiring, image transforms, and migration scripts. Use when building or debugging a Storyblok-backed site in Next.js or Astro, authoring or pushing component schemas, importing or migrating content through the Management API, wiring the Visual Editor or draft mode, or when a…

- **Type:** Skill
- **Install:** `agentstack add skill-kurtschmidt-storyblok-skill-storyblok`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [kurtschmidt](https://agentstack.voostack.com/s/kurtschmidt)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kurtschmidt](https://github.com/kurtschmidt)
- **Source:** https://github.com/kurtschmidt/storyblok-skill/tree/main/skills/storyblok

## Install

```sh
agentstack add skill-kurtschmidt-storyblok-skill-storyblok
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Storyblok Engineering

**Model:** Sonnet for schema authoring, script writing, component implementation, and debugging. Opus only when designing a content model from scratch — the taxonomy and content-type split is the one decision that is expensive to reverse once editors have made content.

Storyblok engineering mechanics for Next.js and Astro. Brand, design-system, and project-specific compliance rules live in their own scoped skills and project rules — do not pull them in here.

## Read first, before writing anything

Storyblok's own docs lag the shipping packages, sometimes by a major version. **When the docs and the installed package disagree, the package wins.** Check `node_modules/@storyblok/*/package.json` and the actual exports before following a doc example. Two of the traps in this skill exist because a doc sample was correct for the previous major.

## The one distinction everything hangs on

Storyblok is two separate APIs with separate hosts, separate auth, and separate rate limits. Conflating them is the most common source of wasted time.

| | Delivery (CDA) | Management (MAPI) |
|---|---|---|
| Host | `api.storyblok.com/v2/cdn` | `mapi.storyblok.com/v1/spaces/` |
| Purpose | Read content to render | Read/write schemas, stories, assets |
| Auth | `token=` query param | `Authorization: ` header, **no `Bearer`** |
| Tokens | public (published only) / preview (reads drafts) | personal access token |
| Rate limit | tiered by query shape | ~6 req/s (plan ceiling, not raisable) |
| Ever in browser | public token, yes | **never** |

A preview token in client-side code leaks every unpublished draft. A management token in client-side code is a full write compromise. Neither belongs in `NEXT_PUBLIC_*` or any bundled Astro client script.

Details: **[references/delivery-api.md](references/delivery-api.md)** and **[references/management-api.md](references/management-api.md)**.

## Non-negotiables

These five hold on every Storyblok project. Each one fails *silently* when violated, which is why they are rules rather than preferences.

1. **Scripts never auto-publish.** Omit the `publish` param on every MAPI write. `POST` without it creates a draft; `PUT` without it saves a draft and leaves the live version serving unchanged. A human publishes. A bulk import that publishes dozens of stories onto a live site is not undoable with a button.

2. **Taxonomy fields live at the ROOT of the content type, never inside a blok.** `filter_query` only works cleanly on root fields. A reference buried in `body` is invisible to every cross-index query, and indexed paths into `body` break the moment an editor reorders the page. `body` is for humans arranging a page; anything a query needs is a typed root field.

3. **Back-links are computed, never authored.** Declare the relationship in one direction on the spoke, compute the inverse with a reverse `filter_query` at build time. A mirrored "related posts" field on the hub is the classic trap: two fields to keep in sync, and nobody will.

4. **The component schema is the contract, for both editors and code.** Constrain in the schema — `component_whitelist`, `customize_toolbar`, required fields, option lists — rather than relying on discipline. An editor, or an agent writing through the MAPI, should be structurally unable to produce a broken page, not merely discouraged from it.

5. **Every page composes from the component library. No page-specific components.** If a page needs something the library lacks, add it to the library, then compose. The test: *if a non-developer could not later rebuild that page in the Storyblok UI, it was built wrong.* That capability is the point of using a CMS at all.

## Component completeness contract

A component is not done when it renders. It is done when a non-developer can find it, recognize it, and fill it in correctly. Every one of these is a build task, not a nicety:

- **Schema pushed**, and the schema filename matches `schema.name`
- **Registered** in the framework's component map (a missing entry renders nothing, with no error)
- **`storyblokEditable` spread on the outermost element** (missing = the block is unclickable in the Visual Editor, with no error anywhere)
- **`component_whitelist`** on every `bloks` field — an unrestricted picker is how unrelated components end up nested
- **Preview image** — without one the block picker is a wall of identical grey rows
- **`preview_field`** so a collapsed block reads like its content, not like its type name
- **`display_name` plus one sentence of help text on every field**
- **Preset** where a block has an obvious default state

Skipping the last four ships a CMS the people who have to run it cannot actually use. Enforce them with a CI check — see **[references/quality-gates.md](references/quality-gates.md)**.

## Working on an existing project

Before changing anything:

1. **Read the schemas, not the docs.** `storyblok/components/*.json`, or wherever the repo keeps them, is the live contract.
2. **Field technical names are effectively immutable once content exists** — including typos. If the schema says `subheadling`, the code says `subheadling`. Renaming means a content migration.
3. **Renaming a component silently breaks `resolve_relations`**, which uses `component_name.field_name` strings. Keep every one of those strings in a single module.
4. **Check whether the project has a decision log.** URL shapes and content-model choices that look like bugs are often deliberate and load-bearing. Confirm before "fixing" one.

## Route by task

| Task | Read |
|---|---|
| Fetching content, versions, `filter_query`, `resolve_relations`, rate limits | [references/delivery-api.md](references/delivery-api.md) |
| Pushing schemas, importing content, assets, migration scripts | [references/management-api.md](references/management-api.md) |
| Designing content types, taxonomy, heading hierarchy, rich text | [references/content-modeling.md](references/content-modeling.md) |
| Next.js App Router: draft mode, revalidation, image loader, preview routes | [references/nextjs.md](references/nextjs.md) |
| Astro: integration config, version selection, bridge, rich text | [references/astro.md](references/astro.md) |
| CI checks that catch the silent failures | [references/quality-gates.md](references/quality-gates.md) |

## Environment variables

Namespace-prefix every Storyblok env var per project (`ACME_STORYBLOK_*`), and never use bare `STORYBLOK_*` on a machine that works on more than one Storyblok site. **Shell environment beats `.env.local`**, so a globally-exported `STORYBLOK_MANAGEMENT_TOKEN` from another project will silently target the wrong space — and a management token pointed at the wrong space does real damage before anyone notices.

Five roles, kept distinct:

| Role | Reads drafts | Safe in browser |
|---|---|---|
| CDA public token | no | yes |
| CDA preview token | yes | **no** |
| Management PAT | n/a (write) | **no** |
| Space ID | n/a | yes |
| Webhook signing secret | n/a | **no** |

Before concluding a token is missing, check the environment and the project's credential store — and test the credential against the endpoint you actually need. A token that authenticates is not necessarily a token scoped for the write you are about to attempt.

## Plan ceilings worth designing inside

Verify against the space's actual plan before relying on any number; they move. The two that bite in practice: **asset count** (a content-heavy site with a real design burns through a few thousand faster than anyone expects — track it from day one) and **scheduled stories** (low single digits on mid tiers, so an editor cannot queue a month of posts; tell them before they discover it). Also check component count, locales, webhook count, and version-history retention against what the build assumes.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kurtschmidt](https://github.com/kurtschmidt)
- **Source:** [kurtschmidt/storyblok-skill](https://github.com/kurtschmidt/storyblok-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kurtschmidt-storyblok-skill-storyblok
- Seller: https://agentstack.voostack.com/s/kurtschmidt
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
