@storyblok/schema
@storyblok/schema is Storyblok’s shared schema layer. It exports TypeScript types and define* helpers for authoring Storyblok content shapes (blocks, fields, folders, datasources, and field plugins) in code. To narrow API client responses, the storyblok schema CLI commands consumes the resulting schema at runtime and compile time.
Requirements
Section titled “Requirements”Node.js LTS (version 22.x recommended)
Installation
Section titled “Installation”Add the package to a project by running this command in the terminal:
npm install @storyblok/schemaDefine a schema with the define* helpers exported by this package, then use the storyblok schema CLI commands to push it to a Storyblok space. The package exposes typed factories for blocks, fields, folders, datasources, and field plugins, plus a defineSchema() wrapper that ties them together.
Each define* helper is a thin, strongly typed identity function: it preserves the literal types of its input so downstream helpers and the Schema type can narrow the content shape. The helpers don’t transform payloads into the Management API wire format, the CLI owns that mapping.
Schema example
Section titled “Schema example”A minimal schema declaration with @storyblok/schema:
import { defineBlock, defineDatasource, defineField, defineFolder, defineSchema, type Schema as InferSchema } from "@storyblok/schema";
const layoutFolder = defineFolder({ name: "Layout" });
const heroBlock = defineBlock({ name: "hero", is_nestable: true, folder: layoutFolder, fields: [defineField("headline", { type: "text", required: true }), defineField("image", { type: "asset", filetypes: ["images"] })],});
const colorsDatasource = defineDatasource({ name: "Colors", slug: "colors" });
export const schema = defineSchema({ blocks: { heroBlock }, folders: { layoutFolder }, datasources: { colorsDatasource },});
export type Schema = InferSchema<typeof schema>;The following sections document each define* helper, validator, and type export.
defineBlock()
Section titled “defineBlock()”defineBlock(block);Returns a typed block (component) definition. The helper preserves the literal types in name and fields so the Schema type can narrow the content shape. Pass fields as an ordered array under fields, and the array index becomes each field’s pos. The helper throws on duplicate field names.
import { defineBlock, defineField } from "@storyblok/schema";
export const pageBlock = defineBlock({ name: "page", is_root: true, fields: [defineField("title", { type: "text", required: true }), defineField("body", { type: "bloks" })],});defineBlock() accepts every property exposed by Storyblok’s component MAPI schema. The most common properties:
| Property | Description |
|---|---|
name |
The technical name of the block. |
fields |
Required. An ordered array of field definitions produced by defineField(). |
display_name |
Optional human-readable name shown in the Storyblok UI. |
is_root |
Marks the block as a content type (root block). Defaults to false. |
is_nestable |
Marks the block as nestable inside bloks fields. Defaults to true. |
folder |
The block folder this block belongs to. Accepts a defineFolder() result, a path string, or null. |
component_group_uuid |
Deprecated. Low-level escape hatch for assigning a block to a group by UUID. Use folder instead. Setting both throws. |
defineField()
Section titled “defineField()”defineField(name, definition);Returns a typed field definition stamped with its name. The type discriminates the accepted properties. For example, an asset field accepts filetypes, while a text field accepts max_length.
import { defineField } from "@storyblok/schema";
const headlineField = defineField("headline", { type: "text", max_length: 120, required: true });const imageField = defineField("image", { type: "asset", filetypes: ["images"] });Besides the Management API field properties, defineField() accepts three more keys and converts each to a plain string in the result:
| Key | Description |
|---|---|
required |
Marks the field as required. |
allow |
Restricts a bloks field to specific blocks or folders. Accepts a defineBlock() result, a block name, a defineFolder() result, or an array thereof. A single allow list restricts by either blocks or folders, not both. |
datasource |
Associates an option/options field with a datasource. Accepts a defineDatasource() result or a datasource slug. |
import { defineField } from "@storyblok/schema";
const bodyField = defineField("body", { type: "bloks", allow: [heroBlock, "teaser"] });const themeField = defineField("theme", { type: "option", source: "internal", datasource: colorsDatasource });Hoist field definitions into reusable variables and reference them from multiple block schemas to maintain a single source of truth for shared fields such as seo_title or body.
The package also exports type-level helpers for working with fields and their values: Field, FieldInput, DefinedField, FieldType, FieldValue, FieldValueInput, AssetFieldValue, BlocksFieldValue, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue, BlockContent, and BlockContentInput.
defineFolder()
Section titled “defineFolder()”defineFolder(folder);Returns a typed block folder (component group) definition. The helper identifies a folder by its resolved path—the display names of its parent folders and itself, joined by /. There’s no UUID: the CLI matches or creates groups by path at push time. The helper throws when the name is empty or contains a /.
import { defineFolder } from "@storyblok/schema";
const layout = defineFolder({ name: "Layout" });const heroes = defineFolder({ name: "Heroes", parent: layout });// heroes.path === "Layout/Heroes"Pass a folder to a block’s folder property, or to a field’s allow list, to reference it. The exported BlockFolder type describes the returned shape.
defineDatasource()
Section titled “defineDatasource()”defineDatasource(datasource);Declares a datasource by name and slug, including optional dimensions for multi-dimensional datasources. Editors write the values for the datasource entry, so the schema ignores them. The package doesn’t model or push them.
import { defineDatasource } from "@storyblok/schema";
export const colorsDatasource = defineDatasource({ name: "Colors", slug: "colors", dimensions: [ { name: "Light Mode", entry_value: "light" }, { name: "Dark Mode", entry_value: "dark" }, ],});The matching exported type is Datasource.
defineFieldPlugin()
Section titled “defineFieldPlugin()”defineFieldPlugin(config);Binds a Storyblok custom field field_type to a Standard Schema validator. The helper infers both the fieldType literal and the value type from the single argument. The value accepts any Standard Schema validator (Zod, Valibot, ArkType, or a hand-written one) and the helper retains it for runtime use.
import { defineFieldPlugin } from "@storyblok/schema";import { z } from "zod";
const colorPicker = defineFieldPlugin({ fieldType: "my-custom-color-picker", value: z.object({ color: z.string(), alpha: z.number() }),});Register field plugins under a fieldPlugins key so the Schema type maps each fieldType to its validator output. The matching exported type is FieldPlugin.
Storyblok also ships official field plugins under the @storyblok/schema/field-plugins sub-path. Import them individually so unused plugins tree-shake away:
import { storyblokColorField } from "@storyblok/schema/field-plugins";storyblokColorField handles Storyblok’s official Colorpicker field (storyblok-colorpicker), and exports its value type as StoryblokColorFieldValue.
defineSchema()
Section titled “defineSchema()”defineSchema(schema);Typed identity wrapper for a schema object, consistent with the other define* helpers. It preserves the exact literal shape via const inference so the Schema type can derive block, datasource, and field-plugin types from typeof schema. It doesn’t validate or transform.
import { defineSchema } from "@storyblok/schema";
export const schema = defineSchema({ blocks: { pageBlock, heroBlock }, folders: { layout }, datasources: { colorsDatasource }, fieldPlugins: { storyblokColorField },});The defineSchema() argument requires blocks and accepts the optional folders, datasources, and fieldPlugins. Each key contains an object of the corresponding define* results.
Schema
Section titled “Schema”type Schema<T>;Derives a schema-types interface from the typeof a schema object. The resulting type exposes blocks, datasources, and fieldPlugins: blocks and datasources become unions of the corresponding define* results, and fieldPlugins becomes a fieldType → validator output map. The derived types exclude folders, used only to organize blocks in the Block library.
import { defineSchema } from "@storyblok/schema";import type { Schema as InferSchema } from "@storyblok/schema";
export const schema = defineSchema({ blocks: { pageBlock, heroBlock }, datasources: { colorsDatasource }, fieldPlugins: { storyblokColorField },});
export type Schema = InferSchema<typeof schema>;export type Blocks = Schema["blocks"];export type Datasources = Schema["datasources"];export type FieldPlugins = Schema["fieldPlugins"];Passing the resulting Schema to createApiClient(...).withTypes<Schema>() narrows cdn/stories/* responses to the union of block content types declared in the schema.
Typed component props
Section titled “Typed component props”Type a framework component’s props by block name. Add a Block helper to your schema.ts, next to the other type exports:
import type { BlockContent } from "@storyblok/schema";
export type Block<TName extends Blocks["name"]> = BlockContent<Extract<Blocks, { name: TName }>, Blocks, FieldPlugins>;Block<"hero"> resolves to the fully typed content of the hero block, with nested blocks and any registered field plugins (custom fields) applied. Blocks["name"] gives autocomplete for every block name. The Block helper is a thin wrapper over the lower-level BlockContent helper, which stays available when you need to pass a block definition directly.
import type { Block } from "../schema";
type Props = { block: Block<"hero"> };
export function Hero({ block }: Props) { return <h1>{block.headline}</h1>;}---import type { Block } from "../schema";
interface Props { block: Block<"hero">;}
const { block } = Astro.props;---
<h1>{block.headline}</h1>Validators
Section titled “Validators”The package ships Zod-powered, non-throwing validators that check schemas and stories:
validateSchema(schema);validateStory(story, schema);createStoryValidator(rootBlock, schema);validateSchema(schema)returns aValidationResultdescribing structural problems in the schema itself.validateStory(story, schema)returns aValidationResultdescribing whether a story’s content conforms to the schema.createStoryValidator(rootBlock, schema)wrapsvalidateStory()as a Standard Schema validator bound to a root block, so a story composes with any Standard Schema tooling (for example, as a tRPC input or a form resolver).
import { validateStory } from "@storyblok/schema";import { schema } from "./schema";
const result = validateStory(story, schema);if (!result.ok) { console.error(result.issues);}A ValidationResult has an ok boolean (true when there are no error-severity issues) and an issues array. Each ValidationIssue includes a severity (error or warning), a machine-readable code, a path, an entity, and a human-readable message. The package exports the related types ValidationResult, ValidationIssue, and ValidationSeverity.
Further resources
Section titled “Further resources”Was this page helpful?
This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.
Get in touch with the Storyblok community