Skip to content

storyblok

@evolve-framework/storyblok ยท v1.0.0

The Storyblok CMS for Evolve: a module that serves the shared content contract (pages, content blocks, navigation and site layout) from a Storyblok space, so a storefront reads content the same way whichever CMS is behind it.

When to use

Use it when Storyblok is your CMS. It runs next to a commerce engine, and its webhook flushes the page cache and publishes content-modified events when editors publish. Each content block comes with the Terraform that provisions its Storyblok component, and your own blocks are added the same way. Use @evolve-framework/contentful or @evolve-framework/payload instead for those CMSes.

Get started

  1. Install the package with the peer dependencies it needs. It comes from the Evolve registry, which needs a one-time sign-in.

    pnpm add @evolve-framework/storyblok @evolve-framework/core fastify graphql graphql-yoga zod
    Requirements7
    @evolve-framework/core>= 1.0.0 < 2.0.0
    @types/node>=22optional
    fastify>= 5.12.1 < 6.0.0
    graphql>= 16.0.0 < 17.0.0
    graphql-yoga>= 5.21.3 < 6.0.0
    msw^2.14.6 || ^3.0.1optional
    zod>= 4.4.0 < 5.0.0

For example:

import { createApplication } from "@evolve-framework/core";
import { cache } from "@evolve-framework/core/cache";
import { NoopPublisher } from "@evolve-framework/core/messaging";
import { StoryblokModule } from "@evolve-framework/storyblok";
import { initContextValue } from "@evolve-framework/storyblok/graphql/context";
// Page cache โ€” falls back to an in-memory cache when no Redis URL is given.
await cache.configure({ url: process.env.REDIS_URL });
const app = await createApplication({
name: "cms-storyblok",
module: new StoryblokModule({
config: {
accessToken: process.env.STORYBLOK_ACCESS_TOKEN!,
managementToken: process.env.STORYBLOK_MANAGEMENT_TOKEN,
spaceId: Number(process.env.STORYBLOK_SPACE_ID),
categoryFolderId: Number(process.env.STORYBLOK_CATEGORY_FOLDER_ID),
},
siteConfigs: [
{ country: "NL", languages: ["nl", "en"] },
{ country: "BE", languages: ["nl", "fr"] },
],
messagePublisher: new NoopPublisher(),
webhookSecret: process.env.STORYBLOK_WEBHOOK_SECRET!,
}),
// Required: the module is a GraphQL module, so a context initializer must
// be supplied. `initContextValue` reads the store context from the request.
graphql: { context: initContextValue },
http: { address: { host: "0.0.0.0", port: 4004 } },
});
await app.start();

Modules

StoryblokModule

Module wiring Storyblok resolvers, typedefs and webhook into a Domain Service.

Pass either a pre-built clients bundle (from createStoryblokClients) or a config object; the module will build its own clients from config during init().

Only one StoryblokModule instance per process is supported -- clients are held as module-scope singletons.

import { StoryblokModule } from "@evolve-framework/storyblok";
new StoryblokModule(options: StoryblokModuleOptions)
OptionTypeDescription
messagePublisherMessagePublisher

Publisher used to emit content-modified events from the webhook. Pass a messagePublisher from a cloud adapter (e.g. new SqsPublisher(...)) or a NoopPublisher to disable publishing.

webhookSecretstring

The webhook secret set on the space's webhook in Storyblok. A delivery whose webhook-signature does not match it is refused with a 401.

siteConfigs?SiteConfig[]

Site configurations used to compute translated paths for catalog and content pages. Defaults to an empty array (no translated paths).

origin?string

Origin name attached to outbound content-modified events emitted by the webhook. Defaults to "cms".

webhookPath?string

URL path the webhook is registered on. Defaults to "/api/webhook".

blocks?ContentBlockDef[]

The complete set of content blocks the module serves. Defaults to the built-in contentBlocks; spread it to add or replace, e.g. blocks: [...contentBlocks, myBlock] (a block whose id matches a built-in replaces it). Each block's mapper is registered and its SDL fragment merged into the served schema.

mappers?((registry: MapperRegistry) => void)

Optional callback invoked with the pre-populated MapperRegistry. Use it to add or override mappers for custom Storyblok components.

articlesPerProduct?number

How many articles a product shows at most. Defaults to 4.

siteRevalidation?SiteRevalidationOptions

The sites whose caches a publish purges: their revalidate URLs and the secret they share with this service. The locales purged are those of siteConfigs. Without it the webhook purges only this service's caches.

clients?StoryblokClients

Clients the module uses as they are, typically from createStoryblokClients. Pass either this or config.

config?StoryblokConfig

Settings the module builds its own clients from with createStoryblokClients during init(). Pass either this or clients.

SiteConfig2The fields of siteConfigs.
FieldTypeDescription
countrystring
languagesstring[]
ContentBlockDef8The fields of blocks.
FieldTypeDescription
idstring

Storyblok component name, e.g. "campaign_banner".

typenamestring

Output GraphQL typename, e.g. "CampaignBanner".

typeDefs?string

SDL fragment for the block's output type (+ its union membership). Omitted for components with no GraphQL surface of their own (link wrappers such as menu_page_link, whose parent resolves them to another type).

terraform?string

Terraform provisioning the component in the Storyblok space. Omitted for a union member that is declared but not provisioned yet.

groups?string[]

Groups this block is a member of (which slots it can fill).

slots?Record<string, Slot>

Slots this block exposes (what it can contain).

mapper?ContentBlockMapper

Maps a Storyblok blok of this component onto the block's GraphQL type. Omitted only for blocks whose component is never mapped on its own -- pages and story singletons (assembled by their resolver) and link wrappers.

pageMapper?PageMapper

Maps a story of this page type onto its GraphQL page. Set only on page types; mapPage dispatches a story to it on its content's component name.

StoryblokClients6The fields of clients.
FieldTypeDescription
clientStoryblok
managementClientStoryblok
spaceIdnumber
categoryFolderIdnumber
accessTokenstring
defaultLanguagestring
StoryblokConfig6The fields of config.
FieldTypeDescription
accessTokenstring

Public delivery API access token.

spaceIdnumber

Storyblok space id.

categoryFolderIdnumber

Parent folder id used when upserting catalog category pages.

managementToken?string

OAuth management token used by event handlers.

region?"eu" | "us" | "ap" | "ca" | "cn"

Storyblok region (defaults to "eu").

defaultLanguage?string

The language whose slug a story carries as its own, with every other language beside it as a translated slug. Defaults to en-GB.

More

It has to be the space's default language: a story's slug is the default language's, so naming a different one here grows a second tree of pages next to the one the space resolves.

Extends AbstractModule<StoryblokLoaderContext>.

Testing

Test helpers in /testing. What they are for

Commands

Installing this package adds these commands to the evolve CLI.

  • evolve eject: Eject built-in blocks into your project so you own them
  • evolve tf-export: Export the built-in content blocks' Terraform to disk

Entry points

@evolve-framework/storyblokStoryblokModule, the built-in content blocks and defineContentBlock for your own, the Storyblok clients, and catalog page sync.53 exports
@evolve-framework/storyblok/graphql/contextThe request context the module's resolvers expect.3 exports
@evolve-framework/storyblok/graphql/typesThe generated GraphQL types of the content contract.237 exports
@evolve-framework/storyblok/sbtypesTypeScript types of the Storyblok components the module reads.44 exports
@evolve-framework/storyblok/testingA test context and block registry, a mocked Storyblok API and a test executor.4 exports