Back to Payload

Import Export Plugin

docs/plugins/import-export.mdx

3.85.139.8 KB
Original Source

This plugin adds features that give admin users the ability to download or create export data as an upload collection and import it back into a project.

<YouTube id="lyAOXO5TcZw" title="A simple, powerful way to manage your data in Payload: Import/Export Plugin" />

Core Features

  • Export data as CSV or JSON format via the admin UI
  • Download the export directly through the browser
  • Create a file upload of the export data
  • Use the jobs queue for large exports
  • Import collection data
  • Preview data before exporting or importing

Installation

Install the plugin using any JavaScript package manager like pnpm, npm, or Yarn:

bash
pnpm add @payloadcms/plugin-import-export

Basic Usage

In the plugins array of your Payload Config, call the plugin with options:

ts
import { buildConfig } from 'payload'
import { importExportPlugin } from '@payloadcms/plugin-import-export'

const config = buildConfig({
  collections: [Users, Pages],
  plugins: [
    importExportPlugin({
      collections: [{ slug: 'users' }, { slug: 'pages' }],
      // see below for a list of available options
    }),
  ],
})

export default config

Jobs Queue Requirements

By default, the plugin uses Payload's Jobs Queue for import and export operations. Queued jobs require a runner to be configured, otherwise imports and exports will stay in "pending" status and never complete.

Configure jobs.autoRun in your Payload config so that queued jobs are processed:

ts
import { buildConfig } from 'payload'
import { importExportPlugin } from '@payloadcms/plugin-import-export'

const config = buildConfig({
  collections: [Users, Pages],
  jobs: {
    autoRun: [
      {
        cron: '*/5 * * * *', // Check every 5 minutes
        queue: 'default',
      },
    ],
  },
  plugins: [
    importExportPlugin({
      collections: [{ slug: 'users' }, { slug: 'pages' }],
    }),
  ],
})

export default config

Alternatively, use allQueues: true to process jobs from all queues:

ts
jobs: {
  autoRun: [
    {
      allQueues: true,
      cron: '*/5 * * * *',
    },
  ],
},

For smaller datasets or testing, you can run imports and exports synchronously by setting disableJobsQueue: true in the per-collection ExportConfig or ImportConfig. This avoids the need for a jobs runner but blocks the request until the operation completes.

Options

PropertyTypeDescription
collectionsarrayCollections to include Import/Export controls in. Array of collection configs with per-collection options. Defaults to all.
debugbooleanIf true, enables debug logging.
exportLimitnumber | functionGlobal maximum documents for export operations. Set to 0 for unlimited (default). Per-collection limits take precedence.
importLimitnumber | functionGlobal maximum documents for import operations. Set to 0 for unlimited (default). Per-collection limits take precedence.
overrideExportCollectionfunctionFunction to override the default export collection. Receives { collection } and returns modified collection config.
overrideImportCollectionfunctionFunction to override the default import collection. Receives { collection } and returns modified collection config.

Per-Collection Configuration

Each item in the collections array can have the following properties:

PropertyTypeDescription
slugstringThe collection slug to configure.
exportboolean | ExportConfigSet to false to disable export, or provide export-specific options.
importboolean | ImportConfigSet to false to disable import, or provide import-specific options.

ExportConfig Options

PropertyTypeDescription
batchSizenumberDocuments per batch during export. Default: 100.
disableDownloadbooleanDisable download button for this collection.
disableJobsQueuebooleanRun exports synchronously for this collection.
disableSavebooleanDisable save button for this collection.
formatstringForce format (csv or json) for this collection.
hooksobjectLifecycle hooks — before and after. See Hooks.
limitnumber | functionMaximum documents to export. Set to 0 for unlimited (default). Overrides global exportLimit.
overrideCollectionfunctionOverride the export collection config for this specific target.

ImportConfig Options

PropertyTypeDescription
batchSizenumberDocuments per batch during import. Default: 100.
defaultVersionStatusstringDefault status for imported docs (draft or published).
disableJobsQueuebooleanRun imports synchronously for this collection.
hooksobjectLifecycle hooks — before and after. See Hooks.
limitnumber | functionMaximum documents to import. Set to 0 for unlimited (default). Overrides global importLimit.
overrideCollectionfunctionOverride the import collection config for this specific target.

Example Configuration

ts
import { importExportPlugin } from '@payloadcms/plugin-import-export'

export default buildConfig({
  plugins: [
    importExportPlugin({
      debug: true,

      // Global limits (0 = unlimited, which is the default)
      exportLimit: 10000,
      importLimit: 5000,

      // Override default export collection (e.g., add access control)
      // This will be used by all collections unless they further override the config
      overrideExportCollection: ({ collection }) => {
        collection.access = {
          ...collection.access,
          read: ({ req }) => req.user?.role === 'admin',
        }
        return collection
      },

      // Per-collection settings
      collections: [
        {
          slug: 'pages',
          export: {
            format: 'csv',
            disableDownload: true,
            limit: 1000, // Override global exportLimit for this collection
          },
          import: {
            defaultVersionStatus: 'draft',
            limit: 500, // Override global importLimit for this collection
          },
        },
        {
          slug: 'posts',
          export: false, // Disable export for posts
        },
      ],
    }),
  ],
})

Collection Visibility

The exports and imports collections are hidden from the admin navigation by default (admin.group: false). Their routes remain accessible (for example /admin/collections/exports and /admin/collections/imports), so you can open them directly or link to them from your app. To list them in the sidebar, use overrideExportCollection and overrideImportCollection to set a group:

ts
import { importExportPlugin } from '@payloadcms/plugin-import-export'

importExportPlugin({
  overrideExportCollection: ({ collection }) => ({
    ...collection,
    admin: {
      ...collection.admin,
      group: 'Data Management',
    },
  }),
  overrideImportCollection: ({ collection }) => ({
    ...collection,
    admin: {
      ...collection.admin,
      group: 'Data Management',
    },
  }),
  collections: [{ slug: 'pages' }],
})

With a group set, the Exports and Imports collections appear in the admin navigation under "Data Management", and you can open saved exports or import documents from there (including downloading saved export files).

Limiting Import and Export Size

You can limit the number of documents that can be imported or exported in a single operation. This helps prevent DDOS-style abuse and protects server resources during large data operations.

Limits can be set globally via exportLimit and importLimit, or per-collection using the limit option in export/import config. Per-collection limits take precedence over global limits. Set to 0 for unlimited (the default).

Dynamic Limits

Limits can be a function that receives the request context, allowing dynamic limits based on user roles or other factors:

ts
importExportPlugin({
  // Dynamic global limit based on user role
  exportLimit: ({ req }) => {
    return req.user?.role === 'admin' ? 50000 : 1000
  },

  collections: [
    {
      slug: 'sensitive-data',
      import: {
        // Per-collection dynamic limit
        limit: ({ req }) => {
          if (req.user?.subscription === 'enterprise') return 100000
          if (req.user?.subscription === 'pro') return 10000
          return 1000
        },
      },
    },
  ],
})

Collection-Specific Import and Export targets

By default, the plugin creates a single exports collection and a single imports collection that handle all import/export operations across your enabled collections. However, you can create separate import and export targets for specific collections by overriding the collection slug.

When you change the slug using the overrideCollection function at the per-collection level, this creates an entirely separate uploads collection for that specific source collection. This is useful when you need:

  • Different access control rules for different data types
  • Separate storage locations for exports
  • Isolated import queues for specific workflows
  • Different admin UI organization

Example: Separate Export Targets

ts
import { importExportPlugin } from '@payloadcms/plugin-import-export'

export default buildConfig({
  plugins: [
    importExportPlugin({
      collections: [
        {
          slug: 'users',
          export: {
            // Create a separate 'user-exports' collection for user data
            overrideCollection: ({ collection }) => {
              return {
                ...collection,
                slug: 'user-exports',
                labels: {
                  singular: 'User Export',
                  plural: 'User Exports',
                },
                access: {
                  // Only super admins can access user exports
                  read: ({ req }) => req.user?.role === 'superadmin',
                  create: ({ req }) => req.user?.role === 'superadmin',
                },
              }
            },
          },
          import: {
            // Create a separate 'user-imports' collection
            overrideCollection: ({ collection }) => {
              return {
                ...collection,
                slug: 'user-imports',
                labels: {
                  singular: 'User Import',
                  plural: 'User Imports',
                },
                access: {
                  read: ({ req }) => req.user?.role === 'superadmin',
                  create: ({ req }) => req.user?.role === 'superadmin',
                },
              }
            },
          },
        },
        {
          slug: 'pages',
          // Pages will use the default 'exports' and 'imports' collections
        },
        {
          slug: 'posts',
          // Posts will also use the default collections
        },
      ],
    }),
  ],
})

In this example:

  • User exports are stored in user-exports collection with restricted access
  • User imports are tracked in user-imports collection
  • Pages and posts share the default exports and imports collections

Combining Top-Level and Per-Collection Overrides

You can combine the top-level overrideExportCollection / overrideImportCollection functions with per-collection overrides. The top-level override is applied first, then the per-collection override:

ts
importExportPlugin({
  // Apply to ALL export collections (both default and custom slugs)
  overrideExportCollection: ({ collection }) => {
    return {
      ...collection,
      admin: {
        ...collection.admin,
        group: 'Data Management',
      },
    }
  },

  collections: [
    {
      slug: 'sensitive-data',
      export: {
        // This override is applied AFTER the top-level override
        overrideCollection: ({ collection }) => {
          return {
            ...collection,
            slug: 'sensitive-exports',
            access: {
              read: () => false, // Completely restrict read access
              create: ({ req }) => req.user?.role === 'admin',
            },
          }
        },
      },
    },
  ],
})

Hooks

Collection-level hooks let you intercept each batch of documents before it is written (to file on export, or to the database on import) and after it has been written. Hooks fire once per batch and work for both csv and json formats.

Hook arguments

All hooks receive:

ArgumentTypeDescription
batchNumbernumberCurrent batch, starting at 1.
dataRecord<string, unknown>[]Transformed batch — flat rows for CSV export, nested docs otherwise.
formatstringExport/import format. Open-ended ('csv' | 'json' | string).
originalDataarraySource data before transformation. Read-only — do not mutate.
reqPayloadRequestThe full request object.
totalBatchesnumberTotal number of batches in this operation.

ImportAfterHook additionally receives result: ImportResult (per-batch counts and errors) instead of data/originalData.

before hooks — modify data

before hooks return the (optionally modified) data array, which replaces the batch going into the write step. Returning an empty array skips the write for that batch without aborting remaining batches.

ts
import { importExportPlugin } from '@payloadcms/plugin-import-export'

importExportPlugin({
  collections: [
    {
      slug: 'users',
      export: {
        hooks: {
          // Mask sensitive fields before the batch is written to file
          before: ({ data, format }) => {
            return data.map((row) => {
              const { passwordHash: _ph, ssn: _ssn, ...safe } = row
              return safe
            })
          },
        },
      },
      import: {
        hooks: {
          // Normalise incoming data before it is written to the database
          before: ({ data }) => {
            return data.map((doc) => ({
              ...doc,
              email:
                typeof doc.email === 'string'
                  ? doc.email.toLowerCase()
                  : doc.email,
            }))
          },
        },
      },
    },
  ],
})

after hooks — logging and observability

after hooks fire after the write has completed. Their return value is ignored — use them for logging, metrics, or notifications.

ts
importExportPlugin({
  collections: [
    {
      slug: 'orders',
      export: {
        hooks: {
          after: ({ batchNumber, totalBatches, data, format, req }) => {
            req.payload.logger.info({
              msg: `Export batch ${batchNumber}/${totalBatches} written`,
              format,
              docsInBatch: data.length,
            })
          },
        },
      },
      import: {
        hooks: {
          after: ({ batchNumber, totalBatches, result, req }) => {
            req.payload.logger.info({
              msg: `Import batch ${batchNumber}/${totalBatches} complete`,
              imported: result.imported,
              updated: result.updated,
              errors: result.errors.length,
            })
          },
        },
      },
    },
  ],
})

Using originalData for context

originalData gives you the raw source before any transformation. For exports it is the original DB documents; for imports it is the raw parsed file rows. This is useful when you need context from the full document while building modified flat rows.

ts
export: {
  hooks: {
    before: ({ data, originalData }) => {
      return data.map((row, i) => ({
        ...row,
        // Add a computed column not present in the DB document
        displayName: `${originalData[i]?.firstName} ${originalData[i]?.lastName}`,
      }))
    },
  },
},

Column name mapping for foreign systems

When importing from or exporting to a foreign system, column names rarely match your Payload field names. Use the batch before hooks to rename keys on the way in or out.

Exporting with renamed CSV columns

Use export.hooks.before to rewrite every row's keys to the foreign system's column names. The returned shape is what gets written to the file.

ts
import { importExportPlugin } from '@payloadcms/plugin-import-export'

const exportRenameMap: Record<string, string> = {
  title: 'Post Title',
  excerpt: 'Summary',
  count: 'View Count',
}

importExportPlugin({
  collections: [
    {
      slug: 'posts',
      export: {
        hooks: {
          before: ({ data }) =>
            data.map((row) => {
              const renamed: Record<string, unknown> = {}
              for (const [key, value] of Object.entries(row)) {
                renamed[exportRenameMap[key] ?? key] = value
              }
              return renamed
            }),
        },
      },
    },
  ],
})

JSON exports work the same way — the hook receives the fully-transformed batch, and the returned keys become the top-level JSON keys.

Importing from foreign CSV columns

Use import.hooks.before to rewrite incoming keys back to Payload field names. The hook receives already-unflattened documents, so foreign top-level keys like Post Title are still present and can be remapped to title before the DB write. Keys you don't include in the returned object are dropped.

ts
const importRenameMap: Record<string, string> = {
  'Post Title': 'title',
  Summary: 'excerpt',
  'View Count': 'count',
}

importExportPlugin({
  collections: [
    {
      slug: 'posts',
      import: {
        hooks: {
          before: ({ data }) =>
            data.map((doc) => {
              const renamed: Record<string, unknown> = {}
              for (const [key, value] of Object.entries(doc)) {
                const payloadKey = importRenameMap[key]
                if (payloadKey) {
                  renamed[payloadKey] = value
                }
                // Foreign keys not present in the rename map are dropped.
              }
              return renamed
            }),
        },
      },
    },
  ],
})

CSV cell values arrive as strings. Payload coerces basic scalar types automatically on write, but if a target field has stricter validation you can coerce inside the hook (renamed.count = Number(value)).

Field-level export rename for shared fields

When a field definition is shared across collections and the rename should travel with the field, use the field's own hooks.beforeExport. Mutate siblingData to add the renamed key and return undefined so the original field key is dropped from the output:

ts
import type { FieldBeforeExportHook } from '@payloadcms/plugin-import-export'

const sharedNameField = {
  name: 'sharedName',
  type: 'text' as const,
  custom: {
    'plugin-import-export': {
      hooks: {
        beforeExport: (({ siblingData, value }) => {
          siblingData['Display Name'] = value
          return undefined
        }) satisfies FieldBeforeExportHook,
      },
    },
  },
}

This pattern is export-only. Field-level hooks.beforeImport does not fire for foreign columns — it's keyed by the incoming column name, so it can't pick up a column whose name doesn't already match a Payload field. For the reverse direction, use the collection-level import.hooks.before recipe above.

Field Options

In addition to collection-level hooks, you can configure field-level export and import behavior directly on any field's custom['plugin-import-export'] config. This is especially useful when:

  • You share a field definition across multiple collections and want the behavior to travel with the field
  • You need to transform a deeply nested field without navigating the full document structure in a collection-level hook
PropertyTypeDescription
disabledbooleanWhen true the field is completely excluded from the import-export plugin.
hooks.beforeExportfunctionRuns before a field value is exported. Works for CSV and JSON.
hooks.beforeImportfunctionRuns before a field value is imported. Works for CSV and JSON.
toCSVfunctionDeprecated — use hooks.beforeExport instead. Will be removed in v4.
fromCSVfunctionDeprecated — use hooks.beforeImport instead. Will be removed in v4.

Disabling Fields

To completely exclude a field from import and export operations:

ts
{
  name: 'internalField',
  type: 'text',
  custom: {
    'plugin-import-export': {
      disabled: true,
    },
  },
}

When a field is disabled:

  • It will not appear in export CSV/JSON files
  • It will be ignored during import operations
  • Nested fields inside disabled parent fields are also excluded

hooks.beforeExport

Use hooks.beforeExport to transform a field's value before it is exported. Works for both csv and json formats.

The beforeExport function receives:

PropertyTypeDescription
columnNamestringColumn/field path, underscore-separated for nested fields (includes array indices).
dataobjectThe top-level document being exported.
formatstring'csv' or 'json'.
siblingDataobjectWritable output at the current level. CSV: the flat row accumulator. JSON: the sibling output object.
siblingDocobjectRead-only source at the current level, before any transformation. Use this to read another sibling's raw value.
valueunknownThe field value from the source document.

Return a value to replace the field, undefined to use default behavior, or mutate siblingData to add extra columns at the same level.

Example — splitting a relationship into multiple columns:

ts
import type { FieldBeforeExportHook } from '@payloadcms/plugin-import-export'

const authorFieldWithHooks = {
  name: 'author',
  type: 'relationship',
  relationTo: 'users',
  custom: {
    'plugin-import-export': {
      hooks: {
        beforeExport: (({ value, columnName, siblingData, format }) => {
          if (
            format === 'csv' &&
            value &&
            typeof value === 'object' &&
            'id' in value &&
            'email' in value
          ) {
            siblingData[`${columnName}_id`] = (
              value as { id: number | string }
            ).id
            siblingData[`${columnName}_email`] = (
              value as { email: string }
            ).email
            return undefined
          }
          return value
        }) satisfies FieldBeforeExportHook,
      },
    },
  },
}

Because the hook is defined on the field itself, you can share authorFieldWithHooks across multiple collections and each will get the same export behavior automatically.

hooks.beforeImport

Use hooks.beforeImport to transform a field's value before it is imported. Works for both csv and json formats.

The beforeImport function receives:

PropertyTypeDescription
columnNamestringColumn/field path.
dataobjectThe full flat row (CSV) or top-level parsed document (JSON) being imported.
formatstring'csv' or 'json'.
siblingDataobjectData at the current level. CSV: same reference as data. JSON: the parent-level object.
siblingDocobjectRead-only source at the current level, before any transformation. CSV: same as data.
valueunknownThe raw value from the import file.

Return the transformed value, undefined to skip, or null to explicitly set null.

ts
import type { FieldBeforeImportHook } from '@payloadcms/plugin-import-export'

{
  name: 'author',
  type: 'relationship',
  relationTo: 'users',
  custom: {
    'plugin-import-export': {
      hooks: {
        beforeImport: (({ value, columnName, data, format }) => {
          if (format === 'csv') {
            const id = data[`${columnName}_id`]
            return id ?? undefined
          }
          return value
        }) satisfies FieldBeforeImportHook,
      },
    },
  },
}

Execution Order

Field-level hooks run before collection-level hooks:

  1. Field-level hooks.beforeExport/hooks.beforeImport: run per-field, per-document, during transformation
  2. Collection-level export.hooks.before/import.hooks.before: run per-batch, on the already-field-transformed data

Migrating from toCSV / fromCSV

toCSV and fromCSV are deprecated and will be removed in v4. Existing hooks continue to work unchanged. New code should use hooks.beforeExport / hooks.beforeImport, which also run for JSON exports and imports.

ts
// Before
custom: {
  'plugin-import-export': {
    toCSV: ({ value, columnName, row, doc }) => {
      row[`${columnName}_id`] = (value as any).id
      return doc.title
    },
    fromCSV: ({ value, data }) => data[`${value}_key`],
  },
},

// After
custom: {
  'plugin-import-export': {
    hooks: {
      beforeExport: ({ value, columnName, siblingData, data, format }) => {
        siblingData[`${columnName}_id`] = (value as any).id
        return data.title
      },
      beforeImport: ({ value, data, format }) => data[`${value}_key`],
    },
  },
},

Virtual Fields

Virtual fields (fields with virtual: true) are handled differently during import and export:

  • Export: Virtual fields ARE included in exports. They contain computed values from hooks.
  • Import: Virtual fields are SKIPPED during import. Since they're computed, they cannot be imported.

Exporting Data

There are four possible ways that the plugin allows for exporting documents, the first two are available in the admin UI from the list view of a collection:

  1. Direct download - Using a POST to /api/exports/download and streams the response as a file download
  2. File storage - Goes to the exports collection as an uploads enabled collection
  3. Local API - A create call to the exports collection: payload.create({ collection: 'exports', data: { collectionSlug: 'pages', format: 'json' } })
  4. Jobs Queue - payload.jobs.queue({ task: 'createCollectionExport', input: parameters })

In the Export drawer you can choose:

  • Download — Streams the export file directly to the browser without saving. Nothing is stored in the exports collection.
  • Save — Creates a document in the exports collection (an upload) so the export is stored and can be reused or downloaded later.

To download a saved export, open the exports collection (go to /admin/collections/exports or, if you made it visible as in Collection Visibility, use the sidebar), open the export document, and use the file download from there. Either the Download or Save option can be disabled per collection via ExportConfig (disableDownload, disableSave).

The UI for creating exports provides options so that users can be selective about which documents to include and also which columns or fields to include.

Selection Modes

When opening the export drawer from a collection's list view, you can choose which documents to export:

ModeDescription
Use all documentsExport the entire collection (respects any limit you set)
Use current filtersExport only documents matching your active list view filters
Use current selectionExport only the documents you've checked in the list view

It is necessary to add access control to the uploads collection configuration using the overrideExportCollection function if you have enabled this plugin on collections with data that some authenticated users should not have access to.

<Banner type="warning"> **Note**: Users who have read access to the upload collection may be able to download data that is normally not readable due to [access control](../access-control/overview). </Banner>

The following parameters are used by the export function to handle requests:

PropertyTypeDescription
formatstringEither csv or json to determine the shape of data exported
limitnumberThe max number of documents to export. Leave empty to export all matching documents.
pagenumberThe page of documents to start from (used with limit for pagination).
sortstringThe field to use for ordering documents (e.g., createdAt, -title for descending).
depthnumberHow deeply to populate relationships. Default: 1. Set to 0 to export IDs only.
localestringThe locale code to query documents or all for multi-locale export.
draftsbooleanWhen true, includes draft versions for collections with drafts enabled.
fieldsstring[]Which collection fields to include in the export. Defaults to all fields.
collectionSlugstringThe collection slug to export from.
whereobjectThe WhereObject used to query documents to export. This is set by making selections or filters from the list view
filenamestringThe name for the exported file (without extension).

Importing Data

The plugin allows importing data from CSV or JSON files. There are several ways to import:

  1. Admin UI - Use the Import drawer from the list view of a collection
  2. File storage - Create an import document in the imports collection with an uploaded file
  3. Local API - Create an import document: payload.create({ collection: 'imports', data: { collectionSlug: 'pages', importMode: 'create' }, file: { ... } })
  4. Jobs Queue - payload.jobs.queue({ task: 'createCollectionImport', input: parameters })

Import Parameters

PropertyTypeDescription
collectionSlugstringThe collection to import into.
importModestringcreate, update, or upsert (default: create).
matchFieldstringThe field to use for matching existing documents during update or upsert operations.
localestringThe locale to use for localized fields.

The matchField option allows you to match documents by a field other than id. For example, if importing users, you could match by email instead of id:

ts
payload.create({
  collection: 'imports',
  data: {
    collectionSlug: 'users',
    importMode: 'upsert',
    matchField: 'email',
  },
  file: csvFile,
})

Import Modes

ModeDescription
createOnly creates new documents. Documents with existing IDs will fail.
updateOnly updates existing documents. Requires id column in CSV. Documents without matching IDs will fail.
upsertCreates new documents or updates existing ones based on id. Most flexible option.

Import Results

After an import completes, the import document is updated with a summary:

PropertyTypeDescription
statusstringpending, processing, completed, or failed
summary.totalnumberTotal number of rows processed
summary.importednumberNumber of successfully imported documents
summary.updatednumberNumber of updated documents (update/upsert modes)
summary.issuesnumberNumber of rows that failed
summary.issueDetailsarrayDetails about each failure

CSV Format

Column Naming Convention

CSV columns use underscore (_) notation to represent nested fields:

Field PathCSV Column Name
titletitle
group.valuegroup_value
array[0].fieldarray_0_field
blocks[0]blocks_0_<blockSlug>_blockType
localized (en)localized_en

Relationship Columns

For relationship fields, the column format varies based on the relationship type:

Relationship TypeColumn(s)
hasOne (monomorphic)fieldName
hasOne (polymorphic)fieldName_relationTo, fieldName_id
hasMany (monomorphic)fieldName_0, fieldName_1, etc.
hasMany (polymorphic)fieldName_0_relationTo, fieldName_0_id

Value Handling

During CSV import, certain values are automatically converted:

CSV ValueConverted ToNotes
true, TRUEtrue (boolean)Case-insensitive
false, FALSEfalse (boolean)Case-insensitive
null, NULLnullUse fromCSV hook to preserve as string
Empty string'' or omittedDepends on field type
Numeric stringsnumberAuto-detected for integers and floats

To preserve literal strings like "null" or "true", use a fromCSV function:

ts
{
  name: 'specialField',
  type: 'text',
  custom: {
    'plugin-import-export': {
      fromCSV: ({ value }) => {
        // Return raw value without automatic conversion
        return value
      },
    },
  },
}

Localized Fields

Single Locale Export

When exporting with a specific locale selected, localized fields appear without a locale suffix:

title,description
"English Title","English Description"

Multi-Locale Export

When exporting with locale set to all, each localized field gets a column per configured locale:

title_en,title_es,title_de,description_en,description_es,description_de
"English","Español","Deutsch","Desc EN","Desc ES","Desc DE"

Importing Localized Fields

For single-locale import, data goes into the locale specified in the import settings:

title,description
"New Title","New Description"

For multi-locale import, use locale suffixes in column names to import multiple locales at once:

title_en,title_es
"English Title","Título en Español"

JSON Format

When using JSON format for import/export:

  • Export: Documents are exported as a JSON array, preserving their nested structure
  • Import: Expects a JSON array of document objects

JSON format preserves the exact structure of your data, including:

  • Nested objects and arrays
  • Rich text (Lexical) structures with numeric properties
  • Relationship references
  • All field types in their native format

Example JSON export:

json
[
  {
    "id": "abc123",
    "title": "My Page",
    "group": {
      "value": "nested value",
      "array": [{ "field1": "item 1" }, { "field2": "item 2" }]
    },
    "blocks": [
      {
        "blockType": "hero",
        "title": "Hero Title"
      }
    ]
  }
]