Skip to content

@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. The resulting schema is consumed at runtime by the storyblok schema CLI commands and at compile time for narrowing API client responses.

  • Node.js LTS (version 22.x recommended)

Add the package to a project by running this command in the terminal:

Terminal window
npm install @storyblok/schema@alpha

Define 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 do not transform payloads into the Management API wire format, the CLI owns that mapping.

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(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. Fields are passed as an ordered array under fields; 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(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"] });

In addition to the wire-shape field properties, defineField() accepts three DSL reference keys that are normalized to plain strings 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(folder);

Returns a typed block folder (component group) definition. A folder is identified by its resolved path, computed eagerly from the parent chain (display names joined with /). There is 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 heros = defineFolder({ name: "Heros", parent: layout });
// heros.path === "Layout/Heros"

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(datasource);

Declares a datasource by name and slug, including optional dimensions for multi-dimensional datasources. Datasource entry values are content authored by editors, so they are not part of the schema and are neither modeled nor pushed here.

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(config);

Binds a Storyblok custom field field_type to a Standard Schema validator. Both the fieldType literal and the value type are inferred from the single argument. The value accepts any Standard Schema validator (Zod, Valibot, ArkType, or hand-written) and is retained 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 schema’s 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 subpath. Import them individually so unused plugins tree-shake away:

import { storyblokColorField } from "@storyblok/schema/field-plugins";

storyblokColorField covers Storyblok’s official Colorpicker field (storyblok-colorpicker); its value type is exported as StoryblokColorFieldValue.

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 does not validate or transform.

import { defineSchema } from "@storyblok/schema";
export const schema = defineSchema({
blocks: { pageBlock, heroBlock },
folders: { layout },
datasources: { colorsDatasource },
fieldPlugins: { storyblokColorField },
});

The config accepts blocks (required) plus optional folders, datasources, and fieldPlugins, each a record of the corresponding define* results.

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. Component groups are a UI concern and are not part of the derived types.

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.

The package ships Zod-powered, non-throwing validators for checking a schema or a story against it:

validateSchema(schema);
validateStory(story, schema);
createStoryValidator(rootBlock, schema);
  • validateSchema(schema) returns a ValidationResult describing structural problems in the schema itself.
  • validateStory(story, schema) returns a ValidationResult describing whether a story’s content conforms to the schema.
  • createStoryValidator(rootBlock, schema) wraps validateStory() 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 carries a severity (error or warning), a machine-readable code, a path, an entity, and a human-readable message. The related types ValidationResult, ValidationIssue, and ValidationSeverity are exported.

Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.