From prompt to CMS-powered website with Lovable and Storyblok
Storyblok is the first headless CMS that works for developers & marketers alike.
Ask Lovable for a landing page and you get one in a couple of minutes. The spacing is right, the gradient is tasteful, and every word of copy sits in a .tsx file. So when marketing wants a new headline, they have two options: ask an LLM to redesign the homepage, or wait for a developer.
So flip it around. Send the brief to the CMS first. Let the CMS design the content model and write the copy, then let Lovable build the UI on top of what already exists. It takes about as long. The difference is that the copy was never in the code.
Two connections make that work. Storyblok’s MCP server lets the agent write to your space while it builds. Lovable’s Storyblok connector lets the finished app read from it at runtime.
What you need
- An empty Storyblok space, and the region it’s in.
- An empty Lovable project.
- Storyblok permissions to generate a Content Delivery token and a personal access token.
- Lovable workspace admin or owner, which is what creating a connection requires.
The division of labor
Storyblok’s MCP server | Lovable’s Storyblok connector | |
|---|---|---|
Lovable calls it | custom chat connector | app + chat connector |
API | Management | Content Delivery (CDN) |
Access | read, write, publish | read only |
Available to | the chat, while you build | the chat and your published app |
Job here | build the model, write the content | render the page |
Lovable already ships a Storyblok connector, which raises a fair question. Why add anything else?
Because that connector only reads. Lovable calls it an app + chat connector, meaning the same connection serves both the chat while you build and your published app. That’s useful, and it’s still read-only. It can’t create a block or write a story. Your content model has to come from somewhere, and that’s the job the MCP server does.
Storyblok hosts the MCP server itself. Neither vendor documents using the two together.
You don’t have to build the blocks and content from Lovable at all. Model them in Storyblok by hand if you’d rather, or with any other MCP client, and the connector reads them just the same.
Step 1: connect the Storyblok MCP server
There’s nothing to install, since the server is hosted. It authenticates with a personal access token, sent as a bearer token. It also ships as a Storyblok Labs project, which means it “may change without notice”.
Create the token first, under My account → Account settings → Personal access tokens. Scope it before you save it. Give it the one space you’re working in, and only the permissions the agent needs. An unscoped token reaches everything your account reaches, and you’re about to paste it into another product.
Then, in Lovable, open Connectors, click + in the top right, and choose MCP server.
- Server name:
Storyblok - Server URL:
https://mcp.labs.storyblok.com/mcp - Authentication: Bearer token or API key, then paste the token
Click Add server.
Lovable now has seven tools: search, describe, execute_readonly, execute_mutating, execute_destructive, upload_asset and upload_asset_finish. None of them are tied to particular endpoints. The agent searches for the operation it needs, reads the schema, then runs it. You describe the outcome and the server works out the calls.
Step 2: send the brief to the CMS
Both blocks below go into the Lovable chat, in a single message. The MCP server is a Lovable chat connector, so Lovable’s agent is what reads the brief and calls Storyblok’s Management API for you.
Send it on its own and you’ve handed an app builder a product brief with no constraints attached. Expect a landing page you didn’t ask for yet.
Write the brief as you would for a person. Mine is deliberately ordinary.
Product brief
Halvard Watch Co. is launching the Tempest 39, a hand-wound mechanical
chronograph. 39mm steel case, sapphire crystal, tachymeter bezel, 30-minute
and 12-hour totalizers. Run of 300 pieces.
We need a product page with:
- A hero with a headline, one line of supporting copy, a product photo, and
a "Reserve yours" call to action.
- Three details: the movement, the case and crystal, and the dial layout.
Each needs a short title and a sentence.
- A closing section with a couple of formatted paragraphs about the limited
run, and a link to join the launch list.
Audience is enthusiasts who read spec sheets before they read copy. Plain
language, no hype. Then, in the same message, ask for the model and the content in one pass.
Using the Storyblok MCP server, set my space up for the brief above.
Don't write any application code yet. This step only touches Storyblok.
First design the content model:
- A `page` content type block with a `body` field of type Blocks.
- Nestable blocks for the hero, the spec grid, an individual spec, and the
closing section.
- Text for single-line copy, Richtext only where the brief asks for
formatted paragraphs, Asset for images.
- On `page.body`, set restrict_components with a component_whitelist
containing only the blocks you create.
Then create a story called "Home" with slug "home" using `page`, fill it
with real copy written from the brief, and publish it.
List the blocks, fields and copy you plan to create, and wait for my
confirmation before calling any mutating tool. That last instruction matters. execute_mutating writes to a real space, and Lovable documents no approval step of its own before a custom MCP server’s write tools run. It’s worth remembering after today, too. The connection stays in your project, so a later prompt reading “tidy up the content model” is a Management API call against a live space.
Mine created four blocks: hero, spec_grid, spec and closing_section. It reused the page content type the space already had and gave it a body field. Inside spec_grid, a specs field is restricted to spec. The closing section got a richtext body, which is where those formatted paragraphs land.
It also made a choice I hadn’t asked for. My prompt mentioned only Text, Richtext and Asset, and it used multilink for the hero’s call to action and the closing link. That’s the right field type for something that might point at a story or at an external URL.
The copy followed the brief. “That number is set by the movement supply we secured, not by a marketing decision” is not how watch marketing usually reads, which was the point of the brief’s last line. Read the copy in Storyblok rather than in the chat, because Storyblok is where an editor will read it.
Then check the model rather than trusting it. Three things in particular.
- The whitelist. Mine allowed
specinpage.bodyas well as insidespec_grid, so an editor can drop a lone spec straight onto the page, outside the grid. Not harmful, but not what the model should say. The setting is the Manage access to nestable blocks toggle in the UI, orrestrict_componentspluscomponent_whitelistover the API. - Leftover blocks. A new space ships with
feature,gridandteaser, so you’ll see blocks nobody asked for. Delete them if you like. The whitelist is what keeps them off your page. - Field names.
nameandslugare built-in story properties, so neither belongs in a schema.
Step 3: add Lovable’s Storyblok connector
In Storyblok, open Settings → Access Tokens. Pick an Access level, name the token, and click Generate.
Choose Public. It returns published content only, which is what a production site needs. The Preview level returns drafts as well, and you’d use it for a staging or editor preview environment rather than this one.
Then, in Lovable, go to Connectors, select Storyblok, and click Add connection. The form asks for four things: a Display name, a Space region, a Public or preview access token, and Who can access this connection. Paste the Public token and click Connect.
Step 4: let Lovable build the UI
Starting from the model pays off here. Lovable can read your real blocks and fields over MCP, so it builds components that match them. There’s nothing to reconcile afterwards.
Read the blocks in my Storyblok space and the "home" story. Build a landing
page for it: one React component per Storyblok block, each taking a `blok`
prop, mapped by the blok's `component` field in a single mapping object.
Fetch the story through the Storyblok connector. Restrained design, plenty
of space, the product photo leading. No hardcoded copy, every string comes
from the story. Notice what you don’t have to do here. You never write a Storyblok API call, and you never put the token in the project.
That’s the connector working. Lovable puts a gateway in the middle of the request, and most app connectors work this way. Your app asks the gateway for the story. The gateway holds your Storyblok token, makes the real call, and hands the content back. The token stays on Lovable’s side of that line, where your code can’t leak it.
So if Lovable offers to install @storyblok/react and fetch with an access token, turn it down. That code talks to api.storyblok.com directly, ignores the connector you just set up, and puts a credential in your repository that you didn’t need.
Rich text is the one exception. Storyblok stores it as a JSON tree rather than HTML, so the closing section’s paragraphs need a renderer no matter what fetched them. Take that from the SDK, for rendering only:
import { StoryblokRichText } from "@storyblok/react";
<StoryblokRichText document={blok.body} /> The prop is document. Older examples use doc, which isn’t recognised, and the paragraphs render as nothing.
What this sets up
The page is finished, but the content model underneath it is the part that lasts. You have blocks with typed fields, a whitelist on page.body, and copy that no longer lives in a component.
That model is also what the Visual Editor runs on. Point it at a deployment that fetches draft content, and an editor can click any section of the page an agent built and change the Tempest 39’s copy in place, without opening Lovable. It needs a preview token, a second environment and the bridge, so it’s real work rather than a checkbox. Nothing here stands in its way.
What the connector changes
Lovable could always build the page. You could always wire a CMS in by hand, too, with a client library, a token and some fetch code. What it lacked was a first-party route to one.
That’s what the Lovable Storyblok connector does. It fetches through Lovable’s gateway rather than from your code. Setting it up is a form, not an integration. So the app you ship carries no credential and no API code, and it still renders content an editor controls. The MCP server does the other half of the work.
Good fit for marketing sites, editorial content, docs, and directory pages with slug-based detail. Anywhere content changes more often than code, and the people changing it aren’t the people who wrote it.