Skip to main content Inicio Creadores 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".
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill webflow-migration-deep-diveEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Más de este repositorio Implement user sign-up and sign-in flows with Clerk.
Use when building authentication UI, customizing sign-in experience,
or implementing OAuth social login.
Trigger with phrases like "clerk sign-in", "clerk sign-up",
"clerk login flow", "clerk OAuth", "clerk social login".
Implement session management and middleware with Clerk.
Use when managing user sessions, configuring route protection,
or implementing token refresh and custom JWT templates.
Trigger with phrases like "clerk session", "clerk middleware",
"clerk route protection", "clerk token", "clerk JWT".
Configure enterprise SSO, role-based access control, and organization management.
Use when implementing SSO integration, configuring role-based permissions,
or setting up organization-level controls.
Trigger with phrases like "clerk SSO", "clerk RBAC",
"clerk enterprise", "clerk roles", "clerk permissions", "clerk organizations".
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.