Skip to main content Accueil Créateurs jeremylongshore claude-code-plugins-plus-skills webflow-migration-deep-dive
webflow-migration-deep-dive Execute major Webflow migrations — from other CMS platforms to Webflow CMS,
between Webflow sites, or large-scale content re-architecture using the Data API v2
bulk endpoints, strangler fig pattern, and data validation.
Trigger with phrases like "migrate to webflow", "webflow migration",
"import into webflow", "webflow replatform", "move content to webflow",
"webflow bulk import", "wordpress to webflow".
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill webflow-migration-deep-diveLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
name webflow-migration-deep-dive description Execute major Webflow migrations — from other CMS platforms to Webflow CMS,
between Webflow sites, or large-scale content re-architecture using the Data API v2
bulk endpoints, strangler fig pattern, and data validation.
Trigger with phrases like "migrate to webflow", "webflow migration",
"import into webflow", "webflow replatform", "move content to webflow",
"webflow bulk import", "wordpress to webflow".
allowed-tools Read, Write, Edit, Bash(npm:*), Bash(npx:*), Bash(node:*) version 1.5.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","design","no-code","webflow"] compatibility Designed for Claude Code
Webflow Migration Deep Dive
Overview
Comprehensive guide for migrating content to Webflow CMS via the Data API v2.
Covers assessment, data mapping, bulk import (100 items/batch), validation,
and rollback. Handles WordPress, Contentful, Strapi, CSV, and JSON source formats.
Prerequisites
webflow-api SDK installed
API token with cms:read and cms:write scopes
Target Webflow site with CMS collections created in the Designer
Source data exported (JSON, CSV, or API access)
Migration Types
Migration Source Complexity Duration CSV/JSON import Static files Low Hours WordPress WP REST API Medium Days Contentful/Strapi Headless CMS API Medium Days Site-to-site Another Webflow site Low Hours Full replatform Custom CMS High Weeks
Instructions
Step 1: Assess Target Collection Schema
Before importing, understand exactly what fields the target collection expects:
import { WebflowClient } from "webflow-api" ;
const webflow = new WebflowClient ({
accessToken : process.env .WEBFLOW_API_TOKEN !,
});
async function assessTarget (siteId : string ) {
const { collections } = await webflow.collections .list (siteId);
const : < , > = {};
( col collections!) {
schema[col. !] = {
: col. ,
: col. ,
: col. ,
: col. ?. ( ({
: f. ,
: f. ,
: f. ,
: f. ,
})),
};
}
. ( . (schema, , ));
schema;
}
schema
Record
string
any
for
const
of
slug
id
id
displayName
displayName
itemCount
itemCount
fields
fields
map
f =>
slug
slug
displayName
displayName
type
type
required
isRequired
console
log
JSON
stringify
null
2
return
Step 2: Build Data Transformer Map source data format to Webflow's fieldData structure:
interface SourcePost {
title : string ;
content : string ;
excerpt : string ;
author : string ;
date : string ;
categories : string [];
featured_image ?: string ;
status : "published" | "draft" ;
}
interface WebflowFieldData {
name : string ;
slug : string ;
[key : string ]: any ;
}
function transformPost (source : SourcePost ): {
fieldData : WebflowFieldData ;
isDraft : boolean ;
} {
return {
isDraft : source.status === "draft" ,
fieldData : {
name : source.title ,
slug : slugify (source.title ),
"post-body" : source.content ,
"excerpt" : source.excerpt ,
"author-name" : source.author ,
"publish-date" : source.date ,
...(source.featured_image && {
"hero-image" : {
url : source.featured_image ,
alt : source.title ,
},
}),
},
};
}
function slugify (text : string ): string {
return text
.toLowerCase ()
.replace (/[^a-z0-9]+/g , "-" )
.replace (/(^-|-$)/g , "" )
.substring (0 , 256 );
}
Step 3: WordPress Migration
async function fetchWordPressPosts (wpUrl : string ): Promise <SourcePost []> {
const posts : SourcePost [] = [];
let page = 1 ;
while (true ) {
const res = await fetch (`${wpUrl} /wp-json/wp/v2/posts?per_page=100&page=${page} ` );
if (!res.ok ) break ;
const wpPosts = await res.json ();
if (wpPosts.length === 0 ) break ;
for (const wp of wpPosts) {
posts.push ({
title : wp.title .rendered ,
content : wp.content .rendered ,
excerpt : wp.excerpt .rendered ,
author : wp.author_name || "Unknown" ,
date : wp.date ,
categories : wp.categories || [],
featured_image : wp.featured_media_url || undefined ,
status : wp.status === "publish" ? "published" : "draft" ,
});
}
page++;
}
return posts;
}
Step 4: CSV Import import { parse } from "csv-parse/sync" ;
import { readFileSync } from "fs" ;
function importFromCSV (filePath : string ): SourcePost [] {
const content = readFileSync (filePath, "utf-8" );
const records = parse (content, {
columns : true ,
skip_empty_lines : true ,
});
return records.map ((row : any ) => ({
title : row.title || row.Title || row.name ,
content : row.content || row.body || row.description || "" ,
excerpt : row.excerpt || row.summary || "" ,
author : row.author || "Imported" ,
date : row.date || row.published_at || new Date ().toISOString (),
categories : (row.categories || row.tags || "" ).split ("," ).map ((s : string ) => s.trim ()),
featured_image : row.image || row.featured_image || undefined ,
status : "published" as const ,
}));
}
Step 5: Bulk Import Engine interface MigrationResult {
total : number ;
created : number ;
skipped : number ;
failed : number ;
errors : Array <{ slug : string ; error : string }>;
duration : number ;
}
async function bulkImport (
collectionId : string ,
sourceItems : SourcePost [],
options = { batchSize: 100 , delayMs: 1000 , dryRun: false }
): Promise <MigrationResult > {
const start = Date .now ();
const result : MigrationResult = {
total : sourceItems.length ,
created : 0 ,
skipped : 0 ,
failed : 0 ,
errors : [],
duration : 0 ,
};
const existing = await fetchAllExistingItems (collectionId);
const existingSlugs = new Set (existing.map (i => i.fieldData ?.slug ));
const newItems = sourceItems
.map (transformPost)
.filter (item => {
if (existingSlugs.has (item.fieldData .slug )) {
result.skipped ++;
return false ;
}
return true ;
});
console .log (`Migration plan: ${newItems.length} new, ${result.skipped} skipped (duplicates)` );
if (options.dryRun ) {
console .log ("DRY RUN — no items will be created" );
result.duration = Date .now () - start;
return result;
}
for (let i = 0 ; i < newItems.length ; i += options.batchSize ) {
const batch = newItems.slice (i, i + options.batchSize );
const batchNum = Math .floor (i / options.batchSize ) + 1 ;
const totalBatches = Math .ceil (newItems.length / options.batchSize );
try {
await webflow.collections .items .createItemsBulk (collectionId, {
items : batch,
});
result.created += batch.length ;
console .log (`Batch ${batchNum} /${totalBatches} : ${batch.length} items created` );
} catch (error : any ) {
result.failed += batch.length ;
result.errors .push ({
slug : `batch-${batchNum} ` ,
error : error.message ,
});
console .error (`Batch ${batchNum} failed:` , error.message );
}
if (i + options.batchSize < newItems.length ) {
await new Promise (r => setTimeout (r, options.delayMs ));
}
}
result.duration = Date .now () - start;
return result;
}
async function fetchAllExistingItems (collectionId : string ) {
const allItems = [];
let offset = 0 ;
while (true ) {
const { items, pagination } = await webflow.collections .items .listItems (
collectionId,
{ offset, limit : 100 }
);
allItems.push (...(items || []));
if (allItems.length >= (pagination?.total || 0 )) break ;
offset += 100 ;
}
return allItems;
}
Step 6: Post-Migration Validation async function validateMigration (
collectionId : string ,
sourceCount : number
): Promise <{ valid : boolean ; checks : Array <{ name : string ; passed : boolean ; detail : string }> }> {
const checks = [];
const { items, pagination } = await webflow.collections .items .listItems (
collectionId, { limit : 1 }
);
const webflowCount = pagination?.total || 0 ;
checks.push ({
name : "Item count" ,
passed : webflowCount >= sourceCount,
detail : `Webflow: ${webflowCount} , Source: ${sourceCount} ` ,
});
const { items : sample } = await webflow.collections .items .listItems (
collectionId, { limit : 10 }
);
const missingFields = (sample || []).filter (
i => !i.fieldData ?.name || !i.fieldData ?.slug
);
checks.push ({
name : "Required fields" ,
passed : missingFields.length === 0 ,
detail : `${missingFields.length} items missing name/slug` ,
});
const allItems = await fetchAllExistingItems (collectionId);
const slugs = allItems.map (i => i.fieldData ?.slug );
const uniqueSlugs = new Set (slugs);
checks.push ({
name : "Unique slugs" ,
passed : slugs.length === uniqueSlugs.size ,
detail : `${slugs.length - uniqueSlugs.size} duplicate slugs` ,
});
const draftCount = allItems.filter (i => i.isDraft ).length ;
checks.push ({
name : "Published items" ,
passed : true ,
detail : `${allItems.length - draftCount} published, ${draftCount} drafts` ,
});
const valid = checks.every (c => c.passed );
return { valid, checks };
}
Step 7: Publish Migrated Content async function publishMigratedContent (collectionId : string ) {
const allItems = await fetchAllExistingItems (collectionId);
const unpublished = allItems.filter (i => !i.isDraft ).map (i => i.id !);
for (let i = 0 ; i < unpublished.length ; i += 100 ) {
const batch = unpublished.slice (i, i + 100 );
await webflow.collections .items .publishItem (collectionId, {
itemIds : batch,
});
console .log (`Published ${Math .min(i + 100 , unpublished.length)} /${unpublished.length} ` );
if (i + 100 < unpublished.length ) {
await new Promise (r => setTimeout (r, 1000 ));
}
}
}
Step 8: Rollback Plan async function rollbackMigration (collectionId : string , createdAfter : Date ) {
const allItems = await fetchAllExistingItems (collectionId);
const migratedItems = allItems.filter (
i => new Date (i.createdOn !) >= createdAfter
);
console .log (`Rolling back ${migratedItems.length} migrated items` );
for (let i = 0 ; i < migratedItems.length ; i += 100 ) {
const batch = migratedItems.slice (i, i + 100 ).map (item => item.id !);
await webflow.collections .items .deleteItemsBulk (collectionId, {
itemIds : batch,
});
console .log (`Deleted batch ${Math .floor(i / 100 ) + 1 } ` );
await new Promise (r => setTimeout (r, 500 ));
}
}
Complete Migration Script
npx tsx migrate.ts --source wordpress --wp-url https://myblog.com --dry-run
npx tsx migrate.ts --source wordpress --wp-url https://myblog.com
npx tsx migrate.ts --validate --collection-id col-xxx
npx tsx migrate.ts --publish --collection-id col-xxx
npx tsx migrate.ts --rollback --collection-id col-xxx --after 2026-03-22
Output
Source data assessment and schema mapping
Data transformer (WordPress, CSV, JSON, headless CMS)
Bulk import engine (100 items/batch with rate limit handling)
Post-migration validation (count, fields, duplicates)
Content publishing automation
Rollback procedure with time-based filtering
Error Handling Error Cause Solution 400 Bad RequestField name mismatch Compare transformer output to collection schema 409 ConflictDuplicate slugs Add suffix or use createOrUpdate pattern 429 Rate LimitedToo fast between batches Increase delayMs Missing images External image URLs blocked Upload to Webflow assets first Truncated HTML Content too long Check Webflow field length limits
Resources
Next Steps This is the final skill in the Webflow pack. For foundational setup, start with
webflow-install-auth.