# Migrate from Contentful to Agility

> Source: https://agilitycms.com/docs/developers/migrate-from-contentful

> Last verified: 2026-10-03 against Contentful's official tooling documentation: the Contentful CLI `space export` command, the `contentful-export`, `contentful-migration` and `contentful.js` libraries, and the `rich-text` packages. Check the current Contentful documentation for your account before relying on a detail here.

This guide applies the phases in [Migrating to Agility](/docs/developers/migrating-to-agility) to a Contentful space. It covers the export options Contentful provides, how its content model maps to Agility, and the parts that need care: references, rich text and localized fields.

## 1. Export from Contentful

| Option | What you get | Notes |
| --- | --- | --- |
| Contentful CLI: `contentful space export` | One JSON file with `contentTypes`, `entries`, `assets`, `locales`, `webhooks` and `editorInterfaces` | Uses a management token. One environment per export (`--environment-id`, default `master`). |
| The `contentful-export` library | The same export, from your own script | Pass a `deliveryToken` to export the latest **published** versions through the Content Delivery API instead. |
| Content Delivery API, for example through `contentful.js` | Entries and assets as your published site sees them | Can return all locales at once and resolve linked entries. |
| Sync API | An initial full sync, then only what changed, using `nextSyncToken` | Useful for the final sync at cutover. |

Options on `space export` worth knowing:

- `--include-drafts` and `--include-archived` are off by default, so drafts and archived entries are left out unless you ask for them.
- `--download-assets` downloads the asset files as well as their metadata. You need the files to upload them to Agility.
- `--skip-content-model` or `--content-only` export only entries and assets, for repeat runs once the model is settled.
- `--query-entries` limits the export to matching entries, for example `content_type=blogPost`.
- `--max-allowed-limit` sets how many items are requested per page (default 1000). Lower it if responses are too large.

**Published or latest?** Exporting through the Management API gives you the latest version of each entry, including changes that were never published. Exporting with a delivery token gives you what is published. Decide which one you are migrating, and tell content owners.

```bash
contentful space export \
  --space-id <space-id> \
  --environment-id master \
  --management-token <token> \
  --download-assets \
  --export-dir ./contentful-export
```

## 2. Map the content model

| Contentful | Agility | Notes |
| --- | --- | --- |
| Content type | Content model plus a container (list) | A content type used as a page building block may be better as a [component model](/docs/developers/component-models). |
| Entry | Content item | Store the entry's `sys.id` in a `SourceID` Text field so re-runs update instead of duplicating. |
| Asset | A file in the Agility media library | Referenced from Image, File, Gallery or List of Files fields. |
| Locale | Agility locale | Map codes explicitly, for example Contentful `en-US` to Agility `en-us`. |
| Environment | Export one environment, usually `master` | |

### Field types

| Contentful field type | Agility field type |
| --- | --- |
| `Symbol` (short text) | Text |
| `Text` (long text) | Multi-Line Text, or a Markdown field if the content is Markdown |
| `RichText` | HTML (see [Rich text](#rich-text) below) |
| `Integer` | Number |
| `Number` | Decimal |
| `Date` | Date/Time |
| `Boolean` | True/False |
| `Location` | Two Decimal fields (latitude and longitude), or a Text field |
| `Object` (JSON) | Multi-Line Text holding the JSON, or a [custom field](/docs/developers/fields) |
| `Link` to an asset | Image or File |
| `Link` to an entry | Linked Content (a dropdown for a single item) |
| `Array` of `Symbol` | Drop-down List if the values come from a fixed set, otherwise linked content to a small list |
| `Array` of `Link` to entries | Linked Content (a search list box, or a nested list) |
| `Array` of `Link` to assets | Gallery, or List of Files |
| `ResourceLink` | Linked Content, after importing the linked resource |

Field settings carry over too: a `required` field should be required in Agility, and an `in` validation (a fixed set of allowed values) becomes the choices of a Drop-down List. See [Fields](/docs/developers/fields) and [Linked Content Field Types](/docs/developers/linked-content-field-types).

## 3. References

In the export, a reference is a link object, not the linked content itself:

```json
"author": { "en-US": { "sys": { "type": "Link", "linkType": "Entry", "id": "5KsDBWseXY6QegucYAoacS" } } }
```

Import in dependency order, keeping a map from Contentful `sys.id` to the Agility content ID (or, for assets, the uploaded `cdn.aglty.io` URL):

1. Assets.
2. Entries that don't reference other entries (authors, categories).
3. Entries that reference them.

If entries reference each other in a cycle, import them in two passes: first save the items with their linked fields empty, then save again with the links filled in. In Agility, a linked-content dropdown stores the linked item's content ID in its `_ValueField` companion and the display text in its `_TextField`.

## 4. Rich text

Contentful stores rich text as a JSON document, not HTML. Contentful's own `@contentful/rich-text-html-renderer` package converts a rich text document to HTML, which you can save to an Agility HTML field. Before you convert:

- Decide how **embedded entries and assets** inside rich text should appear. Render them to HTML, or import them as linked content and keep a reference.
- Use `@contentful/rich-text-links` to list the entries and assets a document links to, so you can import them first.
- After converting, rewrite asset URLs to the uploaded Agility URLs and internal links to the new paths.

## 5. Localized fields

Each Contentful field is either localized or not (`localized: true` or `false`). In an export that includes all locales, every field value is keyed by locale code:

```json
"fields": {
  "title": { "en-US": "Hello", "fr-CA": "Bonjour" },
  "slug":  { "en-US": "hello" }
}
```

In Agility, the same content item has the **same content ID in every locale**. Create the item in your main locale, then save the same content ID in each other locale to create its locale variants. See [Creating Content and Pages in Other Locales](/docs/developers/creating-content-and-pages-in-other-locales). For a field that isn't localized in Contentful, write the same value into each locale variant.

## 6. Import into Agility

Use the Management SDK and the approach in [Migrating Content into Agility with an AI Agent](/docs/developers/migrating-content-with-an-ai-agent#step-5-scale-up-with-a-management-sdk-script): look up existing items by `SourceID`, upload assets, save in sequential batches and check every result. For a reviewable first pass, an AI agent with the Agility MCP Server can read a sample of the export, propose models and import 10 to 20 entries into Staging.

```ts
// One Contentful entry (exported with all locales) to one Agility item per locale.
function toAgilityItems(entry: any, existing: Map<string, number>, localeMap: Record<string, string>) {
	return Object.entries(localeMap).map(([cfLocale, agilityLocale]) => ({
		locale: agilityLocale,
		item: {
			contentID: existing.get(entry.sys.id) ?? -1,
			properties: {definitionName: "BlogPost", referenceName: "BlogPosts"},
			fields: {
				Title: entry.fields.title?.[cfLocale] ?? entry.fields.title?.["en-US"],
				Slug: entry.fields.slug?.[cfLocale] ?? entry.fields.slug?.["en-US"],
				SourceID: entry.sys.id,
			},
		},
	}))
}
```

Save the main locale first so each item gets its content ID, record that ID, then save the other locales with the same content ID. Throughput guidance is in [Migrating to Agility](/docs/developers/migrating-to-agility#throughput-batches-limits-and-tokens).

## 7. Pages and URLs

The URLs of a Contentful-powered site are built by its front end, often from a slug field. In Agility you can rebuild that routing with pages in the sitemap and with dynamic pages, which generate one page per item in a list from a path formula such as the slug. See [Generating Dynamic Pages From Content](/docs/editors/generating-dynamic-pages-from-content).

List the current site's URLs from its sitemap or a crawl, compare them with the new paths, and add a 301 redirect in Agility's [URL Redirections](/docs/editors/url-redirections) for every URL that changes. See [Keep Your Search Rankings Through a Migration](/docs/developers/keep-search-rankings-through-a-migration).

At cutover, use the Sync API's `nextSyncToken` from your last full run to fetch only what changed during the content freeze. See the [Migration Cutover Runbook](/docs/developers/migration-cutover-runbook).

## Checklist

- [ ] You chose published or latest content, and drafts and archived entries are included or excluded on purpose.
- [ ] Asset files were downloaded and uploaded, and no field or rich text still points at the old asset host.
- [ ] Every content type has a model, and every reference resolves in the Agility editor.
- [ ] Rich text renders correctly, including embedded entries and assets.
- [ ] Each locale has the right values, with the same content ID across locales.
- [ ] Counts per content type match the export.
- [ ] Every changed URL has a 301 redirect.

## Related

- [Migrating to Agility](/docs/developers/migrating-to-agility)
- [Migrating Content into Agility with an AI Agent](/docs/developers/migrating-content-with-an-ai-agent)
- [Field Types and What the APIs Return](/docs/developers/field-types-api-reference)
- [Multi-Locale Guide](/docs/developers/multi-locale-guide)
- [Migration Cutover Runbook](/docs/developers/migration-cutover-runbook)

---

## Related pages

- [Migrating to Agility](https://agilitycms.com/docs/developers/migrating-to-agility.md)
- [Migrate from WordPress to Agility](https://agilitycms.com/docs/developers/migrate-from-wordpress.md)
- [Migrate from Sitecore to Agility](https://agilitycms.com/docs/developers/migrate-from-sitecore.md)
- [Migration Cutover Runbook](https://agilitycms.com/docs/developers/migration-cutover-runbook.md)
- [Keep Your Search Rankings Through a Migration](https://agilitycms.com/docs/developers/keep-search-rankings-through-a-migration.md)

Index of every docs page: https://agilitycms.com/docs/llms.txt
