Composability Requires Solution Expertise
I argue why Solution Engineers and Architects are critical to a successful solution.
Storyblok Architect ~ Optimizely Most Valued Professional (OMVP) ~ CMS and Commerce certified
Contact MeA cross-platform CLI that helps you migrate Optimizely content and schemas into Storyblok — automatically and safely.
When moving a customer from one CMS to another, developers often find themselves repeating the same tedious setup work—cataloging content types, recreating fields, aligning validators, and wiring up relationships. These manual steps are time-consuming, error-prone, and inconsistent across environments.
To reduce the amount of repetitive developer effort required, I’ve created a cross-platform CLI that helps teams migrate from Optimizely to Storyblok. We’re rolling this out in phases: Phase 1 focuses on migrating content types (component schemas), while future phases will extend to content (entries) migration and beyond.
Pulls content type definitions from Optimizely (Content Definitions API).
Saves them as a standardized JSON snapshot
Pushes those schemas into Storyblok as components/fields.
Safe to re‑run: only creates/updates what’s different.
Pulls pages and blocks from Optimizely.
Saves them as standardized NDJSON files for consistency and easy diffing.
Pushes those entries into Storyblok, recreating structure and components.
Relationship fields and page/block linking are in progress.
Media asset handling (images, documents, etc.) is also in progress.
DiscoveredSchema model so other CMS sources can be added later💡 Tip: Run cms2storyblok --help or cms2storyblok <command> --help for current flags.
dotnet --info)dotnet tool install -g cms2storyblok
dotnet tool install -g cms2storyblok --prerelease
On macOS/Linux ensure the .NET tools path is on your PATH:
export PATH="$HOME/.dotnet/tools:$PATH"
Update
dotnet tool update -g cms2storyblok --prerelease
Uninstall
dotnet tool uninstall -g cms2storyblok
macOS/Linux (zsh/bash)
export OPTI_BASE_URL="https://<your-opti-host>/api"
export OPTI_TOKEN="<your-opti-api-token>"
cms2storyblok optimizely-schema \
--base-url "$OPTI_BASE_URL" \
--token "$OPTI_TOKEN"
Windows (PowerShell)
$env:OPTI_BASE_URL = "https://<your-opti-host>/api"
$env:OPTI_TOKEN = "<your-opti-api-token>"
cms2storyblok optimizely-schema `
--base-url $env:OPTI_BASE_URL `
--token $env:OPTI_TOKEN
This writes timestamped files like:
./schemas/OptimizelySchema_yyyyMMdd_HHmmss.json
macOS/Linux
export STORYBLOK_MANAGEMENT_TOKEN="<your-storyblok-token>"
export STORYBLOK_SPACE_ID="<your-space-id>"
cms2storyblok schema:push \
--input ./schemas/OptimizelySchema_20250911_114501.json \
--space "$STORYBLOK_SPACE_ID" \
--token "$STORYBLOK_MANAGEMENT_TOKEN"
Windows
$env:STORYBLOK_MANAGEMENT_TOKEN = "<your-storyblok-token>"
$env:STORYBLOK_SPACE_ID = "<your-space-id>"
cms2storyblok schema:push `
--input .\schemas\OptimizelySchema_20250911_114501.json `
--space $env:STORYBLOK_SPACE_ID `
--token $env:STORYBLOK_MANAGEMENT_TOKEN
macOS/Linux
export OPTI_BASE_URL="https://www.hiddenfoundry.com/"
cms2storyblok optimizely-get-content \
--base-url "$OPTI_BASE_URL"
Windows
$env:OPTI_BASE_URL = "https://www.hiddenfoundry.com/"
cms2storyblok optimizely-get-content `
--base-url $env:OPTI_BASE_URL
This command exports pages and blocks from Optimizely and saves them as NDJSON:
./export/content_pages.ndjson
./export/content_blocks.ndjson
You can edit these files before pushing them to Storyblok.
macOS/Linux
export STORYBLOK_SPACE_ID="<your-space-id>"
export STORYBLOK_MANAGEMENT_TOKEN="<your-storyblok-token>"
cms2storyblok storyblok-push-pages-content \
--ndjson ./export/content_pages.ndjson \
--space-id "$STORYBLOK_SPACE_ID" \
--token "$STORYBLOK_MANAGEMENT_TOKEN"
Windows
$env:STORYBLOK_SPACE_ID = "<your-space-id>"
$env:STORYBLOK_MANAGEMENT_TOKEN = "<your-storyblok-token>"
cms2storyblok storyblok-push-pages-content `
--ndjson "C:\temp\export\content_pages.ndjson" `
--space-id $env:STORYBLOK_SPACE_ID `
--token $env:STORYBLOK_MANAGEMENT_TOKEN
macOS/Linux
export STORYBLOK_SPACE_ID="<your-space-id>"
export STORYBLOK_MANAGEMENT_TOKEN="<your-storyblok-token>"
cms2storyblok storyblok-push-blocks-content \
--ndjson ./export/content_blocks.ndjson \
--space-id "$STORYBLOK_SPACE_ID" \
--token "$STORYBLOK_MANAGEMENT_TOKEN"
Windows
$env:STORYBLOK_SPACE_ID = "<your-space-id>"
$env:STORYBLOK_MANAGEMENT_TOKEN = "<your-storyblok-token>"
cms2storyblok storyblok-push-blocks-content `
--ndjson "C:\temp\export\content_blocks.ndjson" `
--space-id $env:STORYBLOK_SPACE_ID `
--token $env:STORYBLOK_MANAGEMENT_TOKEN
💡 Tip: Add --dry-run to preview any command without writing to Storyblok.
# 1. Export schemas from Optimizely
cms2storyblok optimizely-schema --base-url "https://<your-opti-host>/api"
# 2. Push schemas into Storyblok
cms2storyblok schema:push --input ./schemas/OptimizelySchema_*.json \
--space <your-space-id> --token <your-storyblok-token>
# 3. Export content (pages + blocks) from Optimizely
cms2storyblok optimizely-get-content --base-url "https://www.hiddenfoundry.com/"
# 4. Push pages into Storyblok
cms2storyblok storyblok-push-pages-content \
--ndjson ./export/content_pages.ndjson \
--space-id <your-space-id> --token <your-storyblok-token>
# 5. Push blocks into Storyblok
cms2storyblok storyblok-push-blocks-content \
--ndjson ./export/content_blocks.ndjson \
--space-id <your-space-id> --token <your-storyblok-token>
--dry-run first to safely test changes./schemas_2025-10-14/) for diffs and rollback.Issues and PRs welcome. Please include:
dotnet --infoMIT
I argue why Solution Engineers and Architects are critical to a successful solution.
Storyblok’s Visual Editor makes content editing simple, intuitive and powerful
A look at how Storyblok’s documentation sets the tone for a positive developer experience
Discover how a well-structured CMS can cut publishing time, reduce rework, and make future changes e
A practical guide to how Storyblok handles draft and published content, with examples of API usage a