contentful-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Contentful Migration

Contentful 迁移

The
contentful-migration
tool lets you describe and execute content model changes as code. Migrations are TypeScript scripts that create, edit, or delete content types, fields, editor interfaces, and entries.
Install:
bash
npm install contentful-migration
contentful-migration
工具允许你以代码形式描述并执行内容模型变更。迁移是用于创建、编辑或删除内容类型、字段、编辑器界面和条目的TypeScript脚本。
安装:
bash
npm install contentful-migration

Scope

适用范围

This skill covers:
  • Content type and field CRUD operations
  • Field types, validations, and editor interface configuration
  • Entry transformations (in-place transforms, deriving linked entries, cross-type transforms)
  • Tags, annotations, taxonomy validations
  • Editor layouts, sidebar widgets
  • Running migrations via
    npx contentful space migration
    (Contentful CLI) and programmatic API
Do not run migrations with
npx contentful-migration
. Use
contentful-cli
for CLI execution, install it as a dev dependency when needed, and run via
npx contentful ...
.
Not covered: SDK client setup (the contentful-nextjs skill), Contentful concepts and API routing (the contentful-guide skill).
本技能涵盖:
  • 内容类型与字段的增删改查(CRUD)操作
  • 字段类型、验证规则和编辑器界面配置
  • 条目转换(原地转换、派生关联条目、跨类型转换)
  • 标签、注释、分类验证
  • 编辑器布局、侧边栏小部件
  • 通过
    npx contentful space migration
    (Contentful CLI)和程序化API运行迁移
请勿使用
npx contentful-migration
运行迁移。使用
contentful-cli
执行CLI操作,必要时将其安装为开发依赖,并通过
npx contentful ...
运行。
未涵盖内容:SDK客户端设置(对应contentful-nextjs技能)、Contentful概念与API路由(对应contentful-guide技能)。

Contentful MCP note

Contentful MCP 说明

  • For users who want easier agent interaction with Contentful while planning or reviewing migrations, point them to the Contentful MCP server docs:
    https://www.contentful.com/developers/docs/tools/mcp-server/
    .
  • Continue to use
    contentful-migration
    scripts and
    contentful-cli
    for actual migration execution.
  • 若用户希望在规划或审核迁移时更便捷地与Contentful交互,可引导其查看Contentful MCP服务器文档:
    https://www.contentful.com/developers/docs/tools/mcp-server/
  • 实际执行迁移时仍需使用
    contentful-migration
    脚本和
    contentful-cli

Migration Script Format

迁移脚本格式

Every migration file exports a function that receives a
migration
object:
typescript
import type { MigrationFunction } from 'contentful-migration'

const migration: MigrationFunction = (migration) => {
  const blogPost = migration.createContentType('blogPost', {
    name: 'Blog Post',
    description: 'A blog post entry',
    displayField: 'title',
  })

  blogPost.createField('title')
    .name('Title')
    .type('Symbol')
    .required(true)
}

export = migration
The function also receives a
context
object as its second parameter, providing
makeRequest
(direct CMA access),
spaceId
, and
accessToken
. Use
makeRequest
when you need data not available through the migration API.
每个迁移文件都导出一个接收
migration
对象的函数:
typescript
import type { MigrationFunction } from 'contentful-migration'

const migration: MigrationFunction = (migration) => {
  const blogPost = migration.createContentType('blogPost', {
    name: 'Blog Post',
    description: 'A blog post entry',
    displayField: 'title',
  })

  blogPost.createField('title')
    .name('Title')
    .type('Symbol')
    .required(true)
}

export = migration
该函数还会接收第二个参数
context
对象,提供
makeRequest
(直接访问CMA)、
spaceId
accessToken
。当需要迁移API未提供的数据时,可使用
makeRequest

Project state

项目状态

echo "=== Existing migrations ===" && ls migrations/ 2>/dev/null || echo "(no migrations/ directory found)"
echo ""
echo "=== Contentful env vars ===" && grep -h CONTENTFUL .env .env.local 2>/dev/null | sed 's/=.*/=<set>/' || echo "(no Contentful env vars found in .env or .env.local)"
echo "=== Existing migrations ===" && ls migrations/ 2>/dev/null || echo "(no migrations/ directory found)"
echo ""
echo "=== Contentful env vars ===" && grep -h CONTENTFUL .env .env.local 2>/dev/null | sed 's/=.*/=<set>/' || echo "(no Contentful env vars found in .env or .env.local)"

Workflow

工作流程

When writing a migration:
  1. Confirm required env vars first. If values are missing, ask the user to add them to a local
    .env
    file before proceeding.
  2. Assess the change. Identify which content types and fields need to change. Check the current content model in the Contentful web app or via CMA.
  3. Write the migration script. Use the operations below. Prefer chaining over object notation — it gives better error messages with line numbers.
  4. Test in a sandbox environment. Never run untested migrations against production. Create a sandbox environment first:
    contentful environment create --name sandbox --source master
    .
  5. Run the migration. See Running Migrations for CLI and programmatic options.
  6. Verify. Check the content model in the web app. Confirm entries are intact.
编写迁移脚本时:
  1. 首先确认所需环境变量。若缺少值,请用户将其添加到本地
    .env
    文件后再继续。
  2. 评估变更内容。确定需要修改的内容类型和字段。通过Contentful网页应用或CMA查看当前内容模型。
  3. 编写迁移脚本。使用以下操作方法。优先使用链式调用而非对象表示法——链式调用能提供带行号的更清晰错误信息。
  4. 在沙箱环境中测试。切勿在生产环境中运行未测试的迁移。先创建沙箱环境:
    contentful environment create --name sandbox --source master
  5. 运行迁移。查看运行迁移了解CLI和程序化选项。
  6. 验证结果。在网页应用中检查内容模型,确认条目完整无缺。

Required environment variables

所需环境变量

  • CONTENTFUL_SPACE_ID
    - Space ID. Find it in the Contentful web app URL (
    /spaces/<SPACE_ID>/...
    ) or in Space settings -> API keys.
  • CONTENTFUL_MANAGEMENT_ACCESS_TOKEN
    - CMA token used for migrations. Create it in Account settings -> CMA tokens (
    https://app.contentful.com/account/profile/cma_tokens
    ) or from a space-scoped CMA tokens page (
    https://app.contentful.com/spaces/<SPACE_ID>/api/cma_tokens
    ).
  • CONTENTFUL_ENVIRONMENT_ID
    (optional) - Target environment ID (for example
    master
    or
    sandbox
    ) when you want to avoid passing
    --environment-id
    .
If any required value is missing, explicitly ask the user for the missing values and tell them where to find each one.
  • CONTENTFUL_SPACE_ID
    - 空间ID。可在Contentful网页应用URL(
    /spaces/<SPACE_ID>/...
    )或空间设置 -> API密钥中找到。
  • CONTENTFUL_MANAGEMENT_ACCESS_TOKEN
    - 用于迁移的CMA令牌。可在账户设置 -> CMA令牌
    https://app.contentful.com/account/profile/cma_tokens
    )或空间范围的CMA令牌页面(
    https://app.contentful.com/spaces/<SPACE_ID>/api/cma_tokens
    )中创建。
  • CONTENTFUL_ENVIRONMENT_ID
    (可选)- 目标环境ID(例如
    master
    sandbox
    ),设置后无需传递
    --environment-id
    参数。
若缺少任何必填值,请明确向用户索要缺失值,并告知每个值的获取位置。

Content Type Operations

内容类型操作

Create a content type:
typescript
const page = migration.createContentType('page', {
  name: 'Page',
  description: 'A generic page',
  displayField: 'title',
})
Edit an existing content type:
typescript
const page = migration.editContentType('page')
page.description('Updated description')
page.displayField('internalName')
Delete a content type:
typescript
migration.deleteContentType('page')
Content type must have zero entries before deletion. Delete all entries first, or use
transformEntriesToType
to move them.
创建内容类型:
typescript
const page = migration.createContentType('page', {
  name: 'Page',
  description: 'A generic page',
  displayField: 'title',
})
编辑现有内容类型:
typescript
const page = migration.editContentType('page')
page.description('Updated description')
page.displayField('internalName')
删除内容类型:
typescript
migration.deleteContentType('page')
删除内容类型前必须确保其下无条目。需先删除所有条目,或使用
transformEntriesToType
转移条目。

Field Operations

字段操作

Create a field:
typescript
page.createField('title')
  .name('Title')
  .type('Symbol')
  .required(true)
  .localized(true)
Edit an existing field:
typescript
page.editField('title')
  .name('Page Title')
  .required(false)
Delete a field:
typescript
page.deleteField('legacyField')
Deleting a field permanently removes its content from all entries.
Change a field ID:
typescript
page.changeFieldId('oldName', 'newName')
Existing content is preserved — only the ID changes.
Move a field:
typescript
page.moveField('slug').afterField('title')
page.moveField('featured').toTheTop()
page.moveField('metadata').toTheBottom()
page.moveField('author').beforeField('publishDate')
创建字段:
typescript
page.createField('title')
  .name('Title')
  .type('Symbol')
  .required(true)
  .localized(true)
编辑现有字段:
typescript
page.editField('title')
  .name('Page Title')
  .required(false)
删除字段:
typescript
page.deleteField('legacyField')
删除字段会永久移除所有条目中该字段的内容。
修改字段ID:
typescript
page.changeFieldId('oldName', 'newName')
现有内容会被保留——仅字段ID变更。
移动字段位置:
typescript
page.moveField('slug').afterField('title')
page.moveField('featured').toTheTop()
page.moveField('metadata').toTheBottom()
page.moveField('author').beforeField('publishDate')

Field Types Quick Reference

字段类型速查

TypeDescriptionExtra config
Symbol
Short text (max 256 chars)
Text
Long text (max 50,000 chars)
Integer
Whole number
Number
Decimal number
Date
ISO 8601 date/time
Boolean
True/false
Object
Arbitrary JSON
Location
Lat/lon coordinates
RichText
Structured rich text
enabledNodeTypes
,
enabledMarks
validations
Array
List of values or referencesRequires
items
:
{ type, linkType?, validations? }
Link
Single referenceRequires
linkType
:
'Asset'
or
'Entry'
ResourceLink
Cross-space referenceRequires
allowedResources
See API Reference — Field Types for full configuration details.
类型描述额外配置
Symbol
短文本(最多256字符)
Text
长文本(最多50,000字符)
Integer
整数
Number
小数
Date
ISO 8601 日期/时间
Boolean
布尔值(是/否)
Object
任意JSON数据
Location
经纬度坐标
RichText
结构化富文本
enabledNodeTypes
enabledMarks
验证规则
Array
值或引用的列表需指定
items
:
{ type, linkType?, validations? }
Link
单个引用需指定
linkType
:
'Asset'
'Entry'
ResourceLink
跨空间引用需指定
allowedResources
查看API参考——字段类型获取完整配置详情。

Validations Quick Reference

验证规则速查

ValidationApplies toExample
in
Symbol, Integer, Number
{ in: ['draft', 'published', 'archived'] }
unique
Symbol, Integer, Number
{ unique: true }
size
Array, Text, Symbol
{ size: { min: 1, max: 5 } }
range
Integer, Number
{ range: { min: 0, max: 100 } }
regexp
Symbol, Text
{ regexp: { pattern: '^[a-z0-9-]+$' } }
dateRange
Date
{ dateRange: { min: '2020-01-01', max: '2030-12-31' } }
linkContentType
Link, Array of Links
{ linkContentType: ['author', 'organization'] }
linkMimetypeGroup
Link (Asset)
{ linkMimetypeGroup: ['image', 'video'] }
assetFileSize
Link (Asset)
{ assetFileSize: { min: 0, max: 5242880 } }
assetImageDimensions
Link (Asset)
{ assetImageDimensions: { width: { min: 100, max: 2000 } } }
Apply validations via
.validations([...])
on a field. See API Reference — Validations for all options.
验证规则适用字段类型示例
in
Symbol、Integer、Number
{ in: ['draft', 'published', 'archived'] }
unique
Symbol、Integer、Number
{ unique: true }
size
Array、Text、Symbol
{ size: { min: 1, max: 5 } }
range
Integer、Number
{ range: { min: 0, max: 100 } }
regexp
Symbol、Text
{ regexp: { pattern: '^[a-z0-9-]+$' } }
dateRange
Date
{ dateRange: { min: '2020-01-01', max: '2030-12-31' } }
linkContentType
Link、Link数组
{ linkContentType: ['author', 'organization'] }
linkMimetypeGroup
Link(Asset)
{ linkMimetypeGroup: ['image', 'video'] }
assetFileSize
Link(Asset)
{ assetFileSize: { min: 0, max: 5242880 } }
assetImageDimensions
Link(Asset)
{ assetImageDimensions: { width: { min: 100, max: 2000 } } }
通过字段的
.validations([...])
方法应用验证规则。查看API参考——验证规则获取所有选项。

Entry Transformations

条目转换

Transform entries in place:
typescript
migration.transformEntries({
  contentType: 'blogPost',
  from: ['firstName', 'lastName'],
  to: ['fullName'],
  transformEntryForLocale: (fields, locale) => {
    const first = fields.firstName[locale]
    const last = fields.lastName[locale]
    if (!first && !last) return
    return { fullName: `${first || ''} ${last || ''}`.trim() }
  },
})
Options:
shouldPublish
(
true
,
false
, or
'preserve'
— default
'preserve'
).
Derive linked entries:
typescript
migration.deriveLinkedEntries({
  contentType: 'blogPost',
  derivedContentType: 'author',
  from: ['authorName'],
  toReferenceField: 'authorRef',
  derivedFields: ['name'],
  identityKey: (fields) =>
    fields.authorName['en-US'].toLowerCase().replace(/\s+/g, '-'),
  deriveEntryForLocale: (fields, locale) => {
    if (locale !== 'en-US') return
    return { name: fields.authorName[locale] }
  },
})
This creates new
author
entries from existing
blogPost.authorName
data and links them via
authorRef
.
See Patterns — Transform Entries and Patterns — Derive Linked Entries for more examples.
原地转换条目:
typescript
migration.transformEntries({
  contentType: 'blogPost',
  from: ['firstName', 'lastName'],
  to: ['fullName'],
  transformEntryForLocale: (fields, locale) => {
    const first = fields.firstName[locale]
    const last = fields.lastName[locale]
    if (!first && !last) return
    return { fullName: `${first || ''} ${last || ''}`.trim() }
  },
})
可选参数:
shouldPublish
true
false
'preserve'
——默认值为
'preserve'
)。
派生关联条目:
typescript
migration.deriveLinkedEntries({
  contentType: 'blogPost',
  derivedContentType: 'author',
  from: ['authorName'],
  toReferenceField: 'authorRef',
  derivedFields: ['name'],
  identityKey: (fields) =>
    fields.authorName['en-US'].toLowerCase().replace(/\s+/g, '-'),
  deriveEntryForLocale: (fields, locale) => {
    if (locale !== 'en-US') return
    return { name: fields.authorName[locale] }
  },
})
此操作会从现有
blogPost.authorName
数据创建新的
author
条目,并通过
authorRef
建立关联。
查看模式——转换条目模式——派生关联条目获取更多示例。

Editor Interface

编辑器界面

Change the widget for a field:
typescript
const page = migration.editContentType('page')

page.changeFieldControl('slug', 'builtin', 'slugEditor', {
  helpText: 'URL-friendly identifier',
  trackingFieldId: 'title',
})

page.changeFieldControl('category', 'builtin', 'dropdown')
page.changeFieldControl('publishDate', 'builtin', 'datePicker', { format: 'dateonly' })
Widget namespaces:
builtin
,
extension
(UI extensions),
app
(custom apps).
See API Reference — Editor Interface for all built-in widgets and their settings.
修改字段的小部件:
typescript
const page = migration.editContentType('page')

page.changeFieldControl('slug', 'builtin', 'slugEditor', {
  helpText: 'URL-friendly identifier',
  trackingFieldId: 'title',
})

page.changeFieldControl('category', 'builtin', 'dropdown')
page.changeFieldControl('publishDate', 'builtin', 'datePicker', { format: 'dateonly' })
小部件命名空间:
builtin
(内置)、
extension
(UI扩展)、
app
(自定义应用)。
查看API参考——编辑器界面获取所有内置小部件及其设置。

Best Practices

最佳实践

  1. Number migration files sequentially:
    001-create-blog-post.ts
    ,
    002-add-author-field.ts
    ,
    003-transform-categories.ts
    .
  2. One logical change per migration. Easier to debug, revert, and review.
  3. Always test in a sandbox environment before running against production.
  4. Use
    shouldPublish: 'preserve'
    (the default) to maintain existing publish states during transforms.
  5. Prefer chaining over object notation — chaining gives line-level error messages.
  6. Split data transforms from schema changes. First migration changes the schema, second transforms data. This makes each step independently verifiable.
  7. Use
    context.makeRequest
    sparingly — only when the migration API doesn't cover your use case.
  1. 按顺序为迁移文件编号
    001-create-blog-post.ts
    002-add-author-field.ts
    003-transform-categories.ts
  2. 每个迁移仅包含一个逻辑变更。便于调试、回滚和审核。
  3. 始终在沙箱环境中测试,再在生产环境中运行。
  4. 使用
    shouldPublish: 'preserve'
    (默认值)以在转换过程中保留现有发布状态。
  5. 优先使用链式调用而非对象表示法——链式调用能提供行级错误信息。
  6. 将数据转换与架构变更分离。第一个迁移修改架构,第二个迁移转换数据。这样每个步骤都可独立验证。
  7. 谨慎使用
    context.makeRequest
    ——仅当迁移API无法满足需求时使用。

Common Mistakes

常见错误

  • Forgetting
    items
    on Array fields.
    type: 'Array'
    requires an
    items
    property specifying the element type.
  • Deleting a content type with entries. You must delete all entries first, or move them with
    transformEntriesToType
    .
  • Missing
    linkType
    on Link fields.
    type: 'Link'
    requires
    linkType: 'Asset'
    or
    linkType: 'Entry'
    .
  • Running against master. Always test in a sandbox environment. Use
    --environment-id sandbox
    on the CLI.
  • Not handling missing locales in transforms.
    transformEntryForLocale
    is called for every locale — return
    undefined
    to skip.
  • Setting
    displayField
    to a non-Symbol field.
    The display field must be of type
    Symbol
    .
  • Array字段忘记设置
    items
    type: 'Array'
    需要指定
    items
    属性来定义元素类型。
  • 删除包含条目的内容类型。必须先删除所有条目,或使用
    transformEntriesToType
    转移条目。
  • Link字段缺少
    linkType
    type: 'Link'
    需要指定
    linkType: 'Asset'
    linkType: 'Entry'
  • 直接在master环境运行。始终在沙箱环境测试。在CLI中使用
    --environment-id sandbox
    参数。
  • 转换时未处理缺失的语言环境
    transformEntryForLocale
    会针对每个语言环境调用——返回
    undefined
    可跳过该语言环境。
  • 将displayField设置为非Symbol类型字段。显示字段必须为Symbol类型。

References

参考资料

  • API Reference — complete migration API surface
  • Patterns — common migration examples
  • Running Migrations — CLI, programmatic API, CI/CD
  • API参考——完整的迁移API列表
  • 模式——常见迁移示例
  • 运行迁移——CLI、程序化API、CI/CD

Related Skills

相关技能

  • the contentful-guide skill — Contentful concepts, terminology, API routing
  • the contentful-nextjs skill — Next.js integration with Contentful
  • contentful-guide技能——Contentful概念、术语、API路由
  • contentful-nextjs技能——Contentful与Next.js集成