Copied to clipboard!

This appendix provides comprehensive guides for migrating to Vibe CMS from other platforms, evolving your schema, and performing zero-downtime migrations.


1. Migrating from Other CMSs

1.1 From WordPress

Content Export Strategy

WordPress stores content in MySQL tables. Export data using WP REST API or direct database access.

Export Posts via WP REST API:

import { createVibeCMS } from 'vibe-cms-sdk';

const cms = createVibeCMS({
  projectId: 'your-project-id',
  apiKey: 'your-api-key',
  baseUrl: 'https://api.vibe-cms.com'
});

// Fetch WordPress posts
async function fetchWordPressPosts() {
  const response = await fetch('https://your-site.com/wp-json/wp/v2/posts?per_page=100');
  const posts = await response.json();
  return posts;
}

// Map WordPress post to Vibe CMS structure
function mapWordPressPost(wpPost: any) {
  return {
    title: wpPost.title.rendered,
    content: wpPost.content.rendered,
    excerpt: wpPost.excerpt.rendered,
    slug: wpPost.slug,
    published_at: wpPost.date,
    author: wpPost.author,
    status: wpPost.status === 'publish' ? 'published' : 'draft',
    featured_image: wpPost.featured_media
  };
}

// Import to Vibe CMS
async function migrateWordPressContent() {
  const wpPosts = await fetchWordPressPosts();
  
  for (const wpPost of wpPosts) {
    const mappedData = mapWordPressPost(wpPost);
    
    // Create content item in Vibe CMS
    // Note: Use admin API or MCP tools for write operations
    console.log('Migrating:', mappedData.title);
  }
}

Schema Mapping

WordPress Field Vibe CMS Field Type Notes
post_title title text Direct mapping
post_content content markdown Convert HTML to Markdown
post_excerpt excerpt text Direct mapping
post_name slug text URL-friendly slug
post_status status text Map publish/draft
post_date published_at text ISO 8601 format
post_author author text Store author name/ID
Custom Fields Custom fields varies Map ACF fields to Vibe fields

Asset Migration

import { createVibeCMS } from 'vibe-cms-sdk';

const cms = createVibeCMS({
  projectId: 'your-project-id',
  apiKey: 'your-api-key'
});

// Download WordPress media and upload to Vibe CMS
async function migrateWordPressMedia(wpMediaId: number) {
  // 1. Fetch WordPress media metadata
  const wpMedia = await fetch(`https://your-site.com/wp-json/wp/v2/media/${wpMediaId}`);
  const mediaData = await wpMedia.json();
  
  const imageUrl = mediaData.source_url;
  const fileName = mediaData.title.rendered;
  const altText = mediaData.alt_text;
  
  // 2. Upload to Vibe CMS using MCP upload tool
  // Use mcp__vibe-cms-docs__upload_file_from_url
  console.log(`Upload ${fileName} from ${imageUrl}`);
  
  return {
    vibeCmsId: 'new-asset-id',
    originalId: wpMediaId
  };
}

1.2 From Contentful

Content Export Strategy

Contentful provides a robust export API. Use the Content Management API to export entries.

Export Script:

import * as contentful from 'contentful-management';

const client = contentful.createClient({
  accessToken: 'your-management-token'
});

async function exportContentfulSpace(spaceId: string, environmentId = 'master') {
  const space = await client.getSpace(spaceId);
  const environment = await space.getEnvironment(environmentId);
  
  // Export content types (schema)
  const contentTypes = await environment.getContentTypes();
  
  // Export entries (content)
  const entries = await environment.getEntries({ limit: 1000 });
  
  // Export assets
  const assets = await environment.getAssets({ limit: 1000 });
  
  return { contentTypes, entries, assets };
}

Schema Mapping

Contentful's content types map to Vibe CMS collections:

// Contentful Content Type -> Vibe CMS Collection
interface ContentfulContentType {
  sys: { id: string };
  name: string;
  fields: Array<{
    id: string;
    name: string;
    type: string;
    required: boolean;
  }>;
}

function mapContentfulToVibeSchema(contentType: ContentfulContentType) {
  // 1. Create collection using MCP manage_collection tool
  const collectionSlug = contentType.sys.id.toLowerCase().replace(/\s+/g, '-');
  
  console.log('Create collection:', {
    action: 'create',
    slug: collectionSlug,
    name: contentType.name
  });
  
  // 2. Create fields using MCP add_collection_field tool
  for (const field of contentType.fields) {
    const vibeFieldType = mapContentfulFieldType(field.type);
    const vibeInterfaceType = mapContentfulInterface(field.type);
    
    console.log('Add field:', {
      collection_slug: collectionSlug,
      name: field.id,
      field_type: vibeFieldType,
      interface_type: vibeInterfaceType,
      is_required: field.required
    });
  }
}

function mapContentfulFieldType(contentfulType: string): string {
  const typeMap: Record<string, string> = {
    'Symbol': 'text',
    'Text': 'markdown',
    'RichText': 'markdown',
    'Integer': 'number',
    'Number': 'number',
    'Boolean': 'boolean',
    'Date': 'text',
    'Location': 'text',
    'Link': 'file'
  };
  return typeMap[contentfulType] || 'text';
}

function mapContentfulInterface(contentfulType: string): string {
  const interfaceMap: Record<string, string> = {
    'Symbol': 'input',
    'Text': 'textarea',
    'RichText': 'markdown',
    'Integer': 'input',
    'Number': 'input',
    'Boolean': 'input',
    'Link': 'single_file'
  };
  return interfaceMap[contentfulType] || 'input';
}

Content Migration

async function migrateContentfulEntries(entries: any[], assetMap: Map<string, string>) {
  for (const entry of entries) {
    const contentType = entry.sys.contentType.sys.id;
    const locale = entry.sys.locale || 'en-US';
    
    // Map Contentful fields to Vibe CMS data structure
    const vibeData: Record<string, any> = {};
    
    for (const [fieldName, fieldValue] of Object.entries(entry.fields)) {
      // Handle different field types
      if (Array.isArray(fieldValue)) {
        // Array of linked assets or references
        vibeData[fieldName] = fieldValue.map(item => {
          if (item.sys?.linkType === 'Asset') {
            return assetMap.get(item.sys.id) || item.sys.id;
          }
          return item;
        });
      } else if (typeof fieldValue === 'object' && fieldValue.sys?.linkType === 'Asset') {
        // Single asset reference
        vibeData[fieldName] = assetMap.get(fieldValue.sys.id) || fieldValue.sys.id;
      } else {
        vibeData[fieldName] = fieldValue;
      }
    }
    
    // Create content item in Vibe CMS
    console.log(`Migrate entry: ${entry.sys.id} to collection: ${contentType}`);
    console.log('Data:', vibeData);
    
    // Use MCP create_content and update_content_translation tools
  }
}

1.3 From Strapi

Content Export Strategy

Strapi stores content in a database. Export via REST API or direct database access.

Export via Strapi API:

async function exportStrapiContent(strapiUrl: string, apiToken: string) {
  const headers = {
    'Authorization': `Bearer ${apiToken}`,
    'Content-Type': 'application/json'
  };
  
  // 1. Fetch content types
  const contentTypesRes = await fetch(`${strapiUrl}/api/content-type-builder/content-types`, {
    headers
  });
  const contentTypes = await contentTypesRes.json();
  
  // 2. Fetch entries for each content type
  const allContent: Record<string, any[]> = {};
  
  for (const contentType of contentTypes.data) {
    const uid = contentType.uid;
    const apiId = uid.split('.').pop(); // e.g., 'api::article.article' -> 'article'
    
    const entriesRes = await fetch(`${strapiUrl}/api/${apiId}?pagination[limit]=1000`, {
      headers
    });
    const entries = await entriesRes.json();
    
    allContent[apiId] = entries.data;
  }
  
  return allContent;
}

Schema Mapping

function mapStrapiToVibeSchema(strapiContentType: any) {
  const collectionSlug = strapiContentType.apiID;
  const collectionName = strapiContentType.info.displayName;
  
  console.log('Create collection:', {
    action: 'create',
    slug: collectionSlug,
    name: collectionName,
    description: strapiContentType.info.description
  });
  
  // Map Strapi attributes to Vibe CMS fields
  for (const [attrName, attrConfig] of Object.entries(strapiContentType.attributes)) {
    const config: any = attrConfig;
    const vibeFieldType = mapStrapiFieldType(config.type);
    const vibeInterfaceType = mapStrapiInterface(config.type);
    
    console.log('Add field:', {
      collection_slug: collectionSlug,
      name: attrName,
      field_type: vibeFieldType,
      interface_type: vibeInterfaceType,
      is_required: config.required || false
    });
  }
}

function mapStrapiFieldType(strapiType: string): string {
  const typeMap: Record<string, string> = {
    'string': 'text',
    'text': 'markdown',
    'richtext': 'markdown',
    'email': 'text',
    'integer': 'number',
    'biginteger': 'number',
    'float': 'number',
    'decimal': 'number',
    'boolean': 'boolean',
    'date': 'text',
    'time': 'text',
    'datetime': 'text',
    'media': 'file'
  };
  return typeMap[strapiType] || 'text';
}

function mapStrapiInterface(strapiType: string): string {
  const interfaceMap: Record<string, string> = {
    'string': 'input',
    'text': 'textarea',
    'richtext': 'markdown',
    'email': 'input',
    'integer': 'input',
    'biginteger': 'input',
    'float': 'input',
    'decimal': 'input',
    'boolean': 'input',
    'media': 'single_file'
  };
  return interfaceMap[strapiType] || 'input';
}

1.4 From Custom CMS

General Migration Strategy

  1. Audit Existing Content Model

    • Document all content types and their fields
    • Identify relationships and dependencies
    • Map custom fields to Vibe CMS field types
  2. Export Data

    • Write database export scripts
    • Export to JSON or CSV for easier processing
    • Include metadata (created_at, updated_at, author, etc.)
  3. Transform Data

    • Normalize field names (lowercase, underscores)
    • Convert data types to Vibe CMS compatible formats
    • Handle special cases (rich text, references, files)
  4. Import to Vibe CMS

    • Create collections via MCP tools
    • Add fields matching your schema
    • Import content items via MCP tools

Example Custom CMS Migration:

import { createVibeCMS } from 'vibe-cms-sdk';
import * as fs from 'fs';

// 1. Read exported data
const exportedData = JSON.parse(fs.readFileSync('custom-cms-export.json', 'utf-8'));

// 2. Define schema mapping
const schemaMapping = {
  'BlogPost': {
    vibeName: 'blog-posts',
    fields: {
      'Title': { vibeName: 'title', type: 'text', interface: 'input' },
      'Body': { vibeName: 'content', type: 'markdown', interface: 'markdown' },
      'PublishDate': { vibeName: 'published_at', type: 'text', interface: 'input' },
      'FeaturedImage': { vibeName: 'featured_image', type: 'file', interface: 'single_file' }
    }
  }
};

// 3. Create collections and fields
for (const [customType, mapping] of Object.entries(schemaMapping)) {
  console.log(`Creating collection: ${mapping.vibeName}`);
  
  // Use MCP manage_collection tool
  // Then add fields using MCP add_collection_field tool
  
  for (const [customField, vibeField] of Object.entries(mapping.fields)) {
    console.log(`Adding field: ${vibeField.vibeName}`);
  }
}

// 4. Import content
for (const item of exportedData.items) {
  const mapping = schemaMapping[item.type];
  if (!mapping) continue;
  
  const vibeData: Record<string, any> = {};
  
  for (const [customField, value] of Object.entries(item.fields)) {
    const fieldMapping = mapping.fields[customField];
    if (fieldMapping) {
      vibeData[fieldMapping.vibeName] = value;
    }
  }
  
  console.log(`Importing item to ${mapping.vibeName}:`, vibeData);
  // Use MCP create_content and update_content_translation tools
}

2. Schema Evolution

2.1 Adding New Collections

Via MCP Tools:

// Use mcp__vibe-cms-docs__manage_collection
const result = await mcp.manage_collection({
  action: 'create',
  slug: 'blog-posts',
  name: 'Blog Posts',
  description: 'Articles and blog content',
  is_singleton: false
});

Best Practices:

  • Use descriptive, plural nouns for collection names (e.g., 'blog-posts', 'products')
  • Plan your slug carefully - it becomes part of your API URLs
  • Add a description to help team members understand the collection's purpose
  • Use is_singleton: true for unique content (e.g., 'homepage', 'site-settings')

2.2 Adding Fields to Existing Collections

Via MCP Tools:

// Add a new field
const result = await mcp.add_collection_field({
  collection_slug: 'blog-posts',
  name: 'author_bio',
  field_type: 'markdown',
  interface_type: 'markdown',
  is_required: false,
  sort_order: 10
});

Field Types and Compatible Interfaces:

Field Type Compatible Interfaces Use Case
text input, textarea Short text, long text
markdown markdown Rich text content
number input Integers, decimals
boolean input True/false flags
file single_file, multiple_files Images, documents, galleries

Validation Rules:

  • Field names must start with a letter
  • Only lowercase letters, numbers, and underscores allowed
  • Cannot use reserved words: id, created_at, updated_at, status, data, etc.

2.3 Renaming Fields (Safe Pattern)

Vibe CMS does not support direct field renaming. Follow this pattern:

Step 1: Add New Field

await mcp.add_collection_field({
  collection_slug: 'blog-posts',
  name: 'post_title', // new name
  field_type: 'text',
  interface_type: 'input',
  is_required: false
});

Step 2: Migrate Data

import { createVibeCMS } from 'vibe-cms-sdk';

const cms = createVibeCMS({
  projectId: 'your-project-id',
  apiKey: 'your-api-key'
});

async function migrateFieldData() {
  // 1. Fetch all content items
  const items = await cms.collection('blog-posts').all();
  
  // 2. Update each item with new field
  for (const item of items) {
    const oldValue = item.field('title'); // old field
    
    // Update translation with new field
    // Use MCP update_content_translation tool
    console.log(`Migrating item ${item.id}: title -> post_title`);
    console.log(`Value: ${oldValue}`);
  }
}

Step 3: Delete Old Field

// After confirming data migration succeeded
await mcp.delete_collection_field({
  collection_slug: 'blog-posts',
  field_name: 'title', // old field
  confirm: true
});

Important: Always backup your data before deleting fields. Deletion is permanent.


2.4 Changing Field Types

Field type changes require creating a new field and migrating data.

Example: Convert text field to markdown

// 1. Add new field with desired type
await mcp.add_collection_field({
  collection_slug: 'blog-posts',
  name: 'description_markdown',
  field_type: 'markdown',
  interface_type: 'markdown',
  is_required: false
});

// 2. Migrate data (convert plain text to markdown if needed)
async function convertTextToMarkdown() {
  const items = await cms.collection('blog-posts').all();
  
  for (const item of items) {
    const textValue = item.field('description');
    // Simple conversion: wrap paragraphs
    const markdownValue = textValue
      .split('\n\n')
      .map(p => p.trim())
      .join('\n\n');
    
    console.log(`Convert item ${item.id}`);
    // Use MCP update_content_translation to save markdownValue
  }
}

// 3. Delete old field after verification
await mcp.delete_collection_field({
  collection_slug: 'blog-posts',
  field_name: 'description',
  confirm: true
});

// 4. Optionally rename new field back to original name
// (Follow renaming pattern above)

2.5 Reordering Fields

Control field display order in the admin interface.

Via MCP Tools:

// Reorder all fields in a collection
await mcp.reorder_collection_fields({
  collection_slug: 'blog-posts',
  field_names: [
    'title',
    'slug',
    'excerpt',
    'content',
    'featured_image',
    'published_at',
    'author'
  ]
});

Important: You must provide ALL field names in desired order. Missing fields will cause an error.

Update Individual Field Sort Order:

await mcp.update_collection_field({
  collection_slug: 'blog-posts',
  field_name: 'featured_image',
  sort_order: 5
});

3. Complete Schema Evolution Documentation

For a complete treatment of schema migrations, field renaming, type changes, bulk operations, and transaction management, please refer to the full documentation sections on content modeling and data operations.

Key patterns to remember:

  • Backward compatibility is essential
  • Always backup before migrations
  • Test on staging first
  • Document schema changes thoroughly
  • Use MCP tools for consistent, auditable changes

See Also