Skip to main content 홈 크리에이터 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".
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill webflow-migration-deep-dive명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... 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.