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.
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:
- 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.
- Partial versioning or version control for the data structures that your code depends on may require hacks and workarounds.
- 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/schemaInstead of adding the --space argument to every CLI command, create a storyblok.config.ts file in the project root:
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 initThe 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.tsfile 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:
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:
// ...
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.tsIntegrate 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 thetypeofoperator 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:
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.






