feat(plugin-import-export): out of beta and added support for collection-level and field-level hooks#16556
Merged
Merged
Conversation
…ion-level and field-level hooks (#16252) ### Plugin out of beta The import export plugin is now marked as stable and will be moved out of beta. For v4 we will be removing the deprecated APIs but for v3 they will remain supported. ### Why Field-level hooks are essential because: 1. **Co-location** — the transform behavior lives next to the field definition, not in a separate collection config 2. **Reusability** — a shared field config carries its hooks to every collection that uses it, zero duplication 3. **Deeply nested fields** — transforming `group.tabs.namedTab.array[0].field` in a collection-level hook requires manually navigating the full document structure; field-level hooks handle this transparently This PR keeps both levels: field-level hooks for per-field transforms, collection-level hooks for batch-wide operations (masking, logging, filtering). ## Field-level hooks Defined per-field via `custom['plugin-import-export'].hooks`: ```ts import type { FieldBeforeExportHook } from '@payloadcms/plugin-import-export' const authorField = { name: 'author', type: 'relationship', relationTo: 'users', custom: { 'plugin-import-export': { hooks: { // Runs before the field value is written to the export file beforeExport: (({ value, columnName, row, format }) => { if (format === 'csv' && value && typeof value === 'object' && 'id' in value) { row[`${columnName}_id`] = value.id row[`${columnName}_email`] = value.email return undefined // let row mutation take effect } return value }) satisfies FieldBeforeExportHook, // Runs before the field value is written to the database beforeImport: ({ value, columnName, data, format }) => { if (format === 'csv') { return data[`${columnName}_id`] ?? undefined } return value }, }, }, }, } ``` ## Collection-level hooks Defined in the plugin config: ```ts importExportPlugin({ collections: [ { slug: 'users', export: { hooks: { before: ({ data, format }) => { // Mask sensitive fields from the entire batch return data.map(({ passwordHash, ssn, ...safe }) => safe) }, after: ({ batchNumber, totalBatches, req }) => { req.payload.logger.info(`Export batch ${batchNumber}/${totalBatches} written`) }, }, }, }, ], }) ``` #### Execution order field-level `hooks.beforeExport` / `hooks.beforeImport` run first (per-field, per-document), then collection-level `hooks.before` / `hooks.after` run on the already-transformed batch. #### Migration from toCSV / fromCSV toCSV and fromCSV remain fully functional but are deprecated — removed in v4.0. Migration is a 1:1 rename plus the new format parameter: ```ts // Before (deprecated) custom: { 'plugin-import-export': { toCSV: ({ value, row }) => { ... }, fromCSV: ({ value, data }) => { ... }, }, } // After custom: { 'plugin-import-export': { hooks: { beforeExport: ({ value, row, format }) => { ... }, beforeImport: ({ value, data, format }) => { ... }, }, }, } ``` ## What changed ### New types `FieldBeforeExportHook` — field-level export hook type (same args as ToCSVFunction + format) `FieldBeforeImportHook` — field-level import hook type (same args as FromCSVFunction + format) `ToCSVFunction` / `FromCSVFunction` — now deprecated aliases #### JSON format support (new) - Field-level hooks now fire for JSON exports/imports, not just CSV - New applyFieldExportHooks / applyFieldImportHooks utilities traverse nested JSON documents and apply field hooks at each position - Preview handlers also apply field hooks for both CSV and JSON ## Bug fixes - [x] Fixed parentPath key computation in getExportFieldFunctions / getImportFieldFunctions — named tabs inside groups now produce correct underscore-separated keys (pre-existing bug) - [x] Fixed slice(-0) bug in import batchProcessor.ts — ImportAfterHook was receiving all accumulated errors instead of per-batch errors when a batch had zero failures - [x] Removed unused fs import from hooks.int.spec.ts ## Renamed internals - toCSVFunctions → exportFieldHooks throughout all source files - fromCSVFunctions → importFieldHooks throughout all source files - Error messages updated from "toCSVFunction" to "field export hook" ## Checklist - [x] Documentation updated with new API, migration guide, execution order - [x] Test collection PostsWithFieldHooks with field-level hooks - [x] 12 new integration tests (CSV export, JSON export, format parameter, deeply nested fields, CSV import, JSON import, backward compatibility with toCSV, backward compatibility with fromCSV, execution order, reusable field config, priority, default behaviour)
Contributor
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
PatrikKozak
previously approved these changes
May 11, 2026
AlessioGr
approved these changes
May 11, 2026
Member
|
🚀 This is included in version v3.85.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plugin out of beta
The import export plugin is now marked as stable and will be moved out of beta. For v4 we will be removing the deprecated APIs but for v3 they will remain supported.
Why
Field-level hooks are essential because:
group.tabs.namedTab.array[0].fieldin a collection-level hook requires manually navigating the full document structure; field-level hooks handle this transparentlyThis PR keeps both levels: field-level hooks for per-field transforms, collection-level hooks for batch-wide operations (masking, logging, filtering).
Field-level hooks
Defined per-field via
custom['plugin-import-export'].hooks:Collection-level hooks
Defined in the plugin config:
Execution order
field-level
hooks.beforeExport/hooks.beforeImportrun first (per-field, per-document), then collection-levelhooks.before/hooks.afterrun on the already-transformed batch.Migration from toCSV / fromCSV
toCSV and fromCSV remain fully functional but are deprecated — removed in v4.0. Migration is a 1:1 rename plus the new format parameter:
What changed
New types
FieldBeforeExportHook— field-level export hook type (same args as ToCSVFunction + format)FieldBeforeImportHook— field-level import hook type (same args as FromCSVFunction + format)ToCSVFunction/FromCSVFunction— now deprecated aliasesJSON format support (new)
Bug fixes
Renamed internals
Checklist