---
title: "Import/Export Plugin"
aliases: [payload-import-export, plugin-import-export, csv-export-payload]
tags: [payloadcms, plugin, import, export, csv, json, etl, jobs-queue]
sources: [raw/plugins__import-export.md]
created: 2026-05-15
updated: 2026-05-15
---

# Import/Export Plugin

Adds CSV and JSON import/export to the Payload admin UI. Exports can be downloaded directly or saved as upload documents; imports support create/update/upsert modes.

## Installation

```bash
pnpm add @payloadcms/plugin-import-export
```

## Basic Setup

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

buildConfig({
  plugins: [
    importExportPlugin({
      collections: [{ slug: 'users' }, { slug: 'pages' }],
    }),
  ],
})
```

## Jobs Queue — Required

By default all operations run through [[wiki/payloadcms/jobs-queue|Payload's Jobs Queue]]. Without a runner, imports/exports stay `pending` forever.

```ts
jobs: {
  autoRun: [{ cron: '*/5 * * * *', queue: 'default' }],
},
```

For small datasets or testing, bypass the queue per-collection:

```ts
{ slug: 'pages', export: { disableJobsQueue: true }, import: { disableJobsQueue: true } }
```

## Plugin-Level Options

| Property | Type | Description |
|----------|------|-------------|
| `collections` | array | Which collections to enable. Defaults to all. |
| `exportLimit` | `number \| fn` | Global max docs per export (`0` = unlimited). |
| `importLimit` | `number \| fn` | Global max docs per import (`0` = unlimited). |
| `overrideExportCollection` | function | Modify the shared `exports` upload collection (e.g. add access control). |
| `overrideImportCollection` | function | Modify the shared `imports` upload collection. |
| `debug` | boolean | Enable debug logging. |

## Per-Collection Config

```ts
collections: [
  {
    slug: 'pages',
    export: {
      format: 'csv',         // force format
      batchSize: 100,        // docs per batch
      limit: 1000,           // override global exportLimit
      disableDownload: true, // hide Download button
      disableSave: false,    // show Save button
      hooks: { before, after },
    },
    import: {
      defaultVersionStatus: 'draft',
      batchSize: 100,
      limit: 500,
      hooks: { before, after },
    },
  },
  {
    slug: 'posts',
    export: false, // disable export entirely
  },
]
```

## Exporting

Four methods:

1. **Admin UI** — Download (streams directly) or Save (stores in `exports` collection)
2. **Local API** — `payload.create({ collection: 'exports', data: { collectionSlug: 'pages', format: 'json' } })`
3. **Jobs Queue** — `payload.jobs.queue({ task: 'createCollectionExport', input: ... })`
4. **REST** — `POST /api/exports/download`

**Selection modes in UI:** all documents / current filters / checked selection.

**Export parameters:** `format`, `limit`, `page`, `sort`, `depth` (default 1), `locale`, `drafts`, `fields`, `where`, `filename`.

## Importing

Four methods:

1. **Admin UI** — Import drawer from collection list view
2. **Local API** — `payload.create({ collection: 'imports', data: { collectionSlug, importMode }, file })`
3. **Jobs Queue** — `payload.jobs.queue({ task: 'createCollectionImport', input: ... })`
4. **File storage** — upload to `imports` collection directly

**Import modes:**

| Mode | Behaviour |
|------|-----------|
| `create` | Create only — fails on duplicate IDs |
| `update` | Update only — requires `id` column, fails on missing IDs |
| `upsert` | Create or update — most flexible |

Match by non-ID field with `matchField`:

```ts
data: { collectionSlug: 'users', importMode: 'upsert', matchField: 'email' }
```

**Import result fields:** `status`, `summary.total`, `summary.imported`, `summary.updated`, `summary.issues`, `summary.issueDetails`.

## Hooks

Hooks fire **once per batch** for both CSV and JSON.

```ts
export: {
  hooks: {
    // Modify data before write — return modified array
    before: ({ data, format, batchNumber, totalBatches, originalData, req }) => {
      return data.map(row => { const { passwordHash: _, ...safe } = row; return safe })
    },
    // After write — return value ignored, use for logging
    after: ({ batchNumber, totalBatches, data, format, req }) => { ... },
  },
},
import: {
  hooks: {
    before: ({ data }) => data.map(doc => ({ ...doc, email: doc.email?.toLowerCase() })),
    // ImportAfterHook receives `result` instead of `data`
    after: ({ result, req }) => { req.payload.logger.info({ imported: result.imported }) },
  },
},
```

**Execution order:** field-level hooks → collection-level hooks.

### Column Name Mapping (foreign systems)

```ts
// Export: rename Payload keys → foreign column names
before: ({ data }) => data.map(row => {
  const map = { title: 'Post Title', excerpt: 'Summary' }
  return Object.fromEntries(Object.entries(row).map(([k, v]) => [map[k] ?? k, v]))
}),

// Import: rename foreign columns → Payload field names
before: ({ data }) => data.map(doc => {
  const map = { 'Post Title': 'title', 'Summary': 'excerpt' }
  return Object.fromEntries(
    Object.entries(doc).filter(([k]) => map[k]).map(([k, v]) => [map[k], v])
  )
}),
```

## Field-Level Options

Configure via `custom['plugin-import-export']` on any field:

```ts
{
  name: 'internalField',
  type: 'text',
  custom: {
    'plugin-import-export': {
      disabled: true,           // exclude from all import/export
      hooks: {
        beforeExport: ({ value, siblingData, format }) => { ... },
        beforeImport: ({ value, data, format }) => { ... },
      },
    },
  },
}
```

- `beforeExport` — return value to replace field, `undefined` for default, or mutate `siblingData` to add extra columns
- `beforeImport` — return transformed value, `undefined` to skip, `null` to explicitly set null
- **Virtual fields:** exported (computed values), but skipped on import

## CSV Format Details

- Nested fields: `group_value`, `array_0_field`
- Blocks: `blocks_0_<blockSlug>_blockType`
- Localized: `title_en`, `title_es` (multi-locale with `locale: 'all'`)
- Relationship has-one: `fieldName` (monomorphic), `fieldName_relationTo` + `fieldName_id` (polymorphic)
- Relationship has-many: `fieldName_0`, `fieldName_1` (monomorphic)
- Auto-coercion: `"true"/"false"` → boolean, `"null"` → null, numeric strings → number

## Separate Export Collections per Data Type

Override the slug to create isolated upload collections with different access control:

```ts
{
  slug: 'users',
  export: {
    overrideCollection: ({ collection }) => ({
      ...collection,
      slug: 'user-exports',
      access: { read: ({ req }) => req.user?.role === 'superadmin' },
    }),
  },
}
```

Collections sharing the default `exports`/`imports` slug are unaffected.

## Admin UI Visibility

`exports`/`imports` collections are hidden from nav by default. To show them:

```ts
overrideExportCollection: ({ collection }) => ({
  ...collection,
  admin: { ...collection.admin, group: 'Data Management' },
}),
```

## Access Control Warning

Users with read access to the `exports` upload collection can download **all exported data**, bypassing per-document access control on the source collection. Always add access control via `overrideExportCollection`.

## Key Takeaways

- **Jobs Queue required by default** — configure `jobs.autoRun` or set `disableJobsQueue: true` for small/test datasets
- **Four export methods:** admin UI download, admin UI save, Local API, Jobs Queue
- **Three import modes:** `create` / `update` / `upsert`; use `matchField` to match by non-ID field
- **Hooks fire per batch**, not per document — return modified array in `before`, use `after` for logging
- **Column mapping via hooks:** `export.hooks.before` renames out, `import.hooks.before` renames in; field-level `beforeExport` is alternative for shared fields
- **Access control gap:** `exports` upload collection bypasses source-collection ACL — add guards via `overrideExportCollection`
- **Virtual fields:** included in exports (computed), skipped on import
- **CSV auto-coercion:** `"null"` → null, `"true"` → boolean, numeric strings → number; use `beforeImport` hook to preserve literal strings

## Related

- [[wiki/payloadcms/jobs-queue|Jobs Queue — Overview]]
- [[wiki/payloadcms/jobs-queue-queues|Jobs Queue — Queues & Execution]]
- [[wiki/payloadcms/upload|Upload & Media]]
- [[wiki/payloadcms/access-control|Access Control]]
- [[wiki/payloadcms/hooks-collections|Collection Hooks]]
- [[wiki/payloadcms/plugins|Official Plugins]]

## Sources

- `raw/plugins__import-export.md`
- https://payloadcms.com/docs/plugins/import-export
