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.
Install the plugin using any JavaScript package manager like pnpm, npm, or Yarn:
In the plugins array of your Payload Config, call the plugin with options:
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:
Alternatively, use allQueues: true to process jobs from all queues:
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.
Property | Type | Description |
|---|---|---|
| array | Collections to include Import/Export controls in. Array of collection configs with per-collection options. Defaults to all. |
| boolean | If true, enables debug logging. |
| `number \ | function` | Global maximum documents for export operations. Set to |
| `number \ | function` | Global maximum documents for import operations. Set to |
| function | Function to override the default export collection. Receives |
| function | Function to override the default import collection. Receives |
Each item in the collections array can have the following properties:
Property | Type | Description |
|---|---|---|
| string | The collection slug to configure. |
| boolean \ | ExportConfig | Set to |
| boolean \ | ImportConfig | Set to |
Property | Type | Description |
|---|---|---|
| number | Documents per batch during export. Default: |
| boolean | Disable download button for this collection. |
| boolean | Run exports synchronously for this collection. |
| boolean | Disable save button for this collection. |
| string | Force format ( |
| object | Lifecycle hooks — |
| `number \ | function` | Maximum documents to export. Set to |
| function | Override the export collection config for this specific target. |
Property | Type | Description |
|---|---|---|
| number | Documents per batch during import. Default: |
| string | Default status for imported docs ( |
| boolean | Run imports synchronously for this collection. |
| object | Lifecycle hooks — |
| `number \ | function` | Maximum documents to import. Set to |
| function | Override the import collection config for this specific target. |
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:
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).
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).
Limits can be a function that receives the request context, allowing dynamic limits based on user roles or other factors:
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:
In this example:
user-exports collection with restricted accessuser-imports collectionexports and imports collectionsYou can combine the top-level overrideExportCollection / overrideImportCollection functions with per-collection overrides. The top-level override is applied first, then the per-collection override:
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.
All hooks receive:
Argument | Type | Description |
|---|---|---|
| number | Current batch, starting at |
| | Transformed batch — flat rows for CSV export, nested docs otherwise. |
| string | Export/import format. Open-ended (`'csv' \ | 'json' \ | string`). |
| array | Source data before transformation. Read-only — do not mutate. |
| | The full request object. |
| number | Total number of batches in this operation. |
ImportAfterHook additionally receives result: ImportResult (per-batch counts and errors) instead of data/originalData.
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.
after hooks fire after the write has completed. Their return value is ignored — use them for logging, metrics, or notifications.
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.
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.
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.
JSON exports work the same way — the hook receives the fully-transformed batch, and the returned keys become the top-level JSON keys.
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.
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)).
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:
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.
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:
Property | Type | Description |
|---|---|---|
| boolean | When |
| function | Runs before a field value is exported. Works for CSV and JSON. |
| function | Runs before a field value is imported. Works for CSV and JSON. |
| function | Deprecated — use |
| function | Deprecated — use |
To completely exclude a field from import and export operations:
When a field is disabled:
Use hooks.beforeExport to transform a field's value before it is exported. Works for both csv and json formats.
The beforeExport function receives:
Property | Type | Description |
|---|---|---|
| string | Column/field path, underscore-separated for nested fields (includes array indices). |
| object | The top-level document being exported. |
| string | |
| object | Writable output at the current level. CSV: the flat row accumulator. JSON: the sibling output object. |
| object | Read-only source at the current level, before any transformation. Use this to read another sibling's raw value. |
| unknown | The 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:
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.
Use hooks.beforeImport to transform a field's value before it is imported. Works for both csv and json formats.
The beforeImport function receives:
Property | Type | Description |
|---|---|---|
| string | Column/field path. |
| object | The full flat row (CSV) or top-level parsed document (JSON) being imported. |
| string | |
| object | Data at the current level. CSV: same reference as |
| object | Read-only source at the current level, before any transformation. CSV: same as |
| unknown | The raw value from the import file. |
Return the transformed value, undefined to skip, or null to explicitly set null.
Field-level hooks run before collection-level hooks:
hooks.beforeExport/hooks.beforeImport: run per-field, per-document, during transformationexport.hooks.before/import.hooks.before: run per-batch, on the already-field-transformed datatoCSV 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.
Virtual fields (fields with virtual: true) are handled differently during import and export:
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:
POST to /api/exports/download and streams the response as a file downloadexports collection as an uploads enabled collectionpayload.create({ collection: 'exports', data: { collectionSlug: 'pages', format: 'json' } })payload.jobs.queue({ task: 'createCollectionExport', input: parameters })In the Export drawer you can choose:
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.
When opening the export drawer from a collection's list view, you can choose which documents to export:
Mode | Description |
|---|---|
Use all documents | Export the entire collection (respects any limit you set) |
Use current filters | Export only documents matching your active list view filters |
Use current selection | Export 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.
The following parameters are used by the export function to handle requests:
Property | Type | Description |
|---|---|---|
| string | Either |
| number | The max number of documents to export. Leave empty to export all matching documents. |
| number | The page of documents to start from (used with limit for pagination). |
| string | The field to use for ordering documents (e.g., |
| number | How deeply to populate relationships. Default: |
| string | The locale code to query documents or |
| boolean | When |
| string[] | Which collection fields to include in the export. Defaults to all fields. |
| string | The collection slug to export from. |
| object | The WhereObject used to query documents to export. This is set by making selections or filters from the list view |
| string | The name for the exported file (without extension). |
The plugin allows importing data from CSV or JSON files. There are several ways to import:
imports collection with an uploaded filepayload.create({ collection: 'imports', data: { collectionSlug: 'pages', importMode: 'create' }, file: { ... } })payload.jobs.queue({ task: 'createCollectionImport', input: parameters })Property | Type | Description |
|---|---|---|
| string | The collection to import into. |
| string | |
| string | The field to use for matching existing documents during |
| string | The 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:
Mode | Description |
|---|---|
create | Only creates new documents. Documents with existing IDs will fail. |
update | Only updates existing documents. Requires |
upsert | Creates new documents or updates existing ones based on |
After an import completes, the import document is updated with a summary:
Property | Type | Description |
|---|---|---|
| string | |
| number | Total number of rows processed |
| number | Number of successfully imported documents |
| number | Number of updated documents (update/upsert modes) |
| number | Number of rows that failed |
| array | Details about each failure |
CSV columns use underscore (_) notation to represent nested fields:
Field Path | CSV Column Name |
|---|---|
| |
| |
| |
| |
| |
For relationship fields, the column format varies based on the relationship type:
Relationship Type | Column(s) |
|---|---|
hasOne (monomorphic) | |
hasOne (polymorphic) | |
hasMany (monomorphic) | |
hasMany (polymorphic) | |
During CSV import, certain values are automatically converted:
CSV Value | Converted To | Notes |
|---|---|---|
| | Case-insensitive |
| | Case-insensitive |
| | Use |
Empty string | | Depends on field type |
Numeric strings | | Auto-detected for integers and floats |
To preserve literal strings like "null" or "true", use a fromCSV function:
When exporting with a specific locale selected, localized fields appear without a locale suffix:
When exporting with locale set to all, each localized field gets a column per configured locale:
For single-locale import, data goes into the locale specified in the import settings:
For multi-locale import, use locale suffixes in column names to import multiple locales at once:
When using JSON format for import/export:
JSON format preserves the exact structure of your data, including:
Example JSON export: