JoyConf 2026 is back. Content Confidence. Human Connection. Save your spot!

Sneak Peek: Type-Safe Schemas for Fields, Blocks, and Datasources With @storyblok/schema

Storyblok is the first headless CMS that works for developers & marketers alike.

We are about to ship a new, type-safe way for developers to manage fields, stories, and datasources with TypeScript: schema-driven development. Together with our CLI, this new package, @storyblok/schema, is the foundation of Storyblok's developer-focused ecosystem.

Hint:

We continue to support existing CLI or UI-based workflows. @storyblok/schema is another tool in your toolbox—something that you can use if you want.

Since @storyblok/schema touches on many different parts of Storyblok, we release it gradually and iterate based on the feedback we collect from you. Take the new package for a spin, tell us what you think, and take advantage of this sneak peek into the future of building with Storyblok.

The challenge: code comes second

The frontend code that powers any Storyblok project transforms content models—fields, blocks, and datasources—into components. While the frontend depends on this exact structure, the current approach delegates component management to Storyblok's UI and Management API.

Developers could only pull types into code via storyblok types generate, but were unable to shape content models via code. This approach works for many projects, but has some potential downsides:

  1. An implicit, brittle dependency on data defined in the block library means that a single absent-minded change to a field or a block could break the frontend code in unexpected ways.
  2. Partial versioning or version control for the data structures that your code depends on may require hacks and workarounds.
  3. Inverted type safety, where types are generated for code instead of from code, can make refactoring more of a hassle.

The new schema package unlocks an alternative approach that puts developer experience (DX) first and gives developers full control over the components at the project's core.

The solution: @storyblok/schema

Our upcoming schema package provides functions and types to define Storyblok schema objects for your fields, blocks, and datasources right inside your code. You forge a single source of truth for all content models within a space via TypeScript objects, and push changes from the comfort of your terminal.

This new approach is type-safe, works with all version control systems, and keeps your frontend code in sync with the data in your Storyblok space. It also helps you author type-safe frontend components and serves as valuable information for your army of coding agents.

While @storyblok/schema is still in preview and some details could change, we don’t anticipate major breaking changes. Time to experience the future DX.

Try it yourself

Experimenting with the schema package requires only a JavaScript package manager and a Storyblok space.

Generate a schema from a space

Your journey into Storyblok schemas starts with installing the latest versions of the Storyblok CLI and @storyblok/schema:

npm install -D storyblok @storyblok/schema

Instead of adding the --space argument to every CLI command, create a storyblok.config.ts file in the project root:

storyblok.config.ts
import { defineConfig } from "storyblok/config";

export default defineConfig({
  space: "1337", // your space ID
});

The CLI lets you generate schemas from an existing space. Authenticate with the CLI, and then run the schema init command:

npx storyblok login --token=YOUR_PERSONAL_ACCESS_TOKEN
npx storyblok schema init

The schema init command generates objects for all components in your space, similar to storyblok types generate. This is a one-time bootstrap procedure after which your code is the single source of truth for the shape all components in the affected space. Since updating components in the blocks library after this point can lead to conflicts, it’s better to stick to either schemas or the Storyblok UI.

Let’s take a closer look at what you’ve got after running schema init:

  • .storyblok/schema/blocks contains TypeScript files that define fields, blocks, and stories.
  • .storyblok/schema/datasources contains TypeScript files that define datasources.
  • The .storyblok/schema.ts file imports and combines all schema definitions into a top-level schema. This is now the source of truth for the project's types.

Create a new schema

The schema files use defineField(), defineBlock(), and defineDatasource() to describe fields, blocks, and datasources, respectively. The functions’ strict type checks help you update or create schemas.

To introduce a new block type with schema-driven development, create a file, define the block and its fields, and add the schema to .storyblok/schema.ts.

Let’s add some internet-friendly cat content to your project. In .storyblok/schema/blocks, create a file called cat.ts and define the schema:

cat.ts
import { defineBlock, defineField } from "@storyblok/schema";

export const catBlock = defineBlock({
  name: "cat",
  is_root: true,
  is_nestable: true,
  fields: [
    defineField("name", {
      type: "text",
    }),
    defineField("picture", {
      type: "asset",
    }),
  ],
});

The snippet describes a universal block (is_root and is_nestable are both true) that has two fields: a text field for the cat's name and an asset field for the picture.

Next, open .storyblok/schema.ts, import catBlock, and add it to the blocks property in the schema object:

.storyblok/schema.ts
// ...

import { catBlock } from "./components/cat";

export const schema = {
  blocks: {
    featureBlock,
    gridBlock,
    pageBlock,
    teaserBlock,
    catBlock, // the new schema object
  },
};

// ...

Finally, to upload the new structure to your space, run schema push, another command available in the CLI’s latest version:

npx storyblok schema push .storyblok/schema/schema.ts

// To view diffs without applying changes:
npx storyblok schema push --dry-run .storyblok/schema/schema.ts

Integrate schemas into framework components

Schema files become the single source of truth for the content models that govern your Storyblok project—both the space and the frontend. Previously, you used storyblok types generate to pull types; now you derive them directly from schemas.

However, a schema defines a type that’s built to interface with Storyblok, not with Vue, React, or any other framework. This is where the schema package’s utility type BlockContent<TBlock, TBlocks> comes in.

The utility type BlockContent<TBlock, TBlocks> can extract component input types from schema objects. It takes up to two arguments:

  • TBlock: the schema type used to derive component types. To access the required type, apply the typeof operator to the schema object (for example, catBlock).
  • TBlocks: optionally the Blocks type exported from the entry file.

Since all schemas connect in the entry file, it is reasonable to import the top-level schema object, pick out your block schema, and pass it into BlockContent:

import type { BlockContent } from "@storyblok/schema";
import { type schema } from "./.storyblok/schema/schema";

type Cat = BlockContent<typeof schema.blocks.catBlock>;

The resulting Cat type contains only the data that's relevant for a frontend component:

  • The _uid attached to all Storyblok components.
  • A component property that identifies the block as "cat".
  • The properties name (a string) and picture (an asset) defined in the schema object.

Now, the type is ready for use in a frontend framework.

Following are basic example implementations in Vue and React:

./components/Cat.tsx
import type { BlockContent } from "@storyblok/schema";
import { type schema } from "../.storyblok/schema/schema";

type CatProps = {
	blok: BlockContent<typeof schema.blocks.catBlock>;
};

export function Cat({ blok }: CatProps) {
	return (
		<figure class="cat">
			{blok.picture?.filename ? (
				<img src={blok.picture.filename} alt={blok.picture.alt ?? ""} />
			) : null}
			<figcaption>{{ blok.name ?? "Anonymous cat" }}</figcaption>
		</figure>
	);
}

This illustrates the power of schema-driven development: the schema objects serve as the single source of truth for your entire project—from the content in your space to the types in your frontend components. No matter what you do and which framework you use, code and content always stay in sync.

What's next?

Experiment with the new package, check out the documentation, stress-test the features, and let us know what you think. What works well, what’s missing, which other use cases for schemas and types come to mind?

Your feedback helps us shape the new developer experience for Storyblokers worldwide.