feat(plugin-import-export): adds support for disabling fields (#13166)

### What?

Adds support for excluding specific fields from the import-export plugin
using a custom field config.

### Why?

Some fields should not be included in exports or previews. This feature
allows users to flag those fields directly in the field config.

### How?

- Introduced a `plugin-import-export.disabled: true` custom field
property.
- Automatically collects and stores disabled field accessors in
`collection.admin.custom['plugin-import-export'].disabledFields`.
- Excludes these fields from the export field selector, preview table,
and final export output (CSV/JSON).
This commit is contained in:
Patrik
2025-07-14 17:10:36 -04:00
committed by GitHub
parent f4d951dd04
commit 5839cb61fa
5 changed files with 88 additions and 7 deletions

View File

@@ -27,7 +27,14 @@ export const FieldsToExport: SelectFieldClientComponent = (props) => {
const { query } = useListQuery()
const collectionConfig = getEntityConfig({ collectionSlug: collectionSlug ?? collection })
const fieldOptions = reduceFields({ fields: collectionConfig?.fields })
const disabledFields =
collectionConfig?.admin?.custom?.['plugin-import-export']?.disabledFields ?? []
const fieldOptions = reduceFields({
disabledFields,
fields: collectionConfig?.fields,
})
useEffect(() => {
if (id || !collectionSlug) {

View File

@@ -43,10 +43,12 @@ const combineLabel = ({
}
export const reduceFields = ({
disabledFields = [],
fields,
labelPrefix = null,
path = '',
}: {
disabledFields?: string[]
fields: ClientField[]
labelPrefix?: React.ReactNode
path?: string
@@ -66,6 +68,7 @@ export const reduceFields = ({
return [
...fieldsToUse,
...reduceFields({
disabledFields,
fields: field.fields,
labelPrefix: combineLabel({ field, prefix: labelPrefix }),
path: createNestedClientFieldPath(path, field),
@@ -83,6 +86,7 @@ export const reduceFields = ({
return [
...tabFields,
...reduceFields({
disabledFields,
fields: tab.fields,
labelPrefix,
path: isNamedTab ? createNestedClientFieldPath(path, field) : path,
@@ -98,6 +102,11 @@ export const reduceFields = ({
const val = createNestedClientFieldPath(path, field)
// If the field is disabled, skip it
if (disabledFields.includes(val)) {
return fieldsToUse
}
const formattedField = {
id: val,
label: combineLabel({ field, prefix: labelPrefix }),

View File

@@ -46,6 +46,14 @@ export const Preview = () => {
(collection) => collection.slug === collectionSlug,
)
const disabledFieldsUnderscored = React.useMemo(() => {
return (
collectionConfig?.admin?.custom?.['plugin-import-export']?.disabledFields?.map((f: string) =>
f.replace(/\./g, '_'),
) ?? []
)
}, [collectionConfig])
const isCSV = format === 'csv'
React.useEffect(() => {
@@ -95,7 +103,10 @@ export const Preview = () => {
const regex = fieldToRegex(field)
return allKeys.filter((key) => regex.test(key))
})
: allKeys.filter((key) => !defaultMetaFields.includes(key))
: allKeys.filter(
(key) =>
!defaultMetaFields.includes(key) && !disabledFieldsUnderscored.includes(key),
)
const fieldKeys =
Array.isArray(fields) && fields.length > 0
@@ -136,7 +147,18 @@ export const Preview = () => {
}
void fetchData()
}, [collectionConfig, collectionSlug, draft, fields, i18n, limit, locale, sort, where])
}, [
collectionConfig,
collectionSlug,
disabledFieldsUnderscored,
draft,
fields,
i18n,
limit,
locale,
sort,
where,
])
return (
<div className={baseClass}>

View File

@@ -108,6 +108,17 @@ export const createExport = async (args: CreateExportArgs) => {
fields: collectionConfig.flattenedFields,
})
const disabledFieldsDot =
collectionConfig.admin?.custom?.['plugin-import-export']?.disabledFields ?? []
const disabledFields = disabledFieldsDot.map((f: string) => f.replace(/\./g, '_'))
const filterDisabled = (row: Record<string, unknown>): Record<string, unknown> => {
for (const key of disabledFields) {
delete row[key]
}
return row
}
if (download) {
if (debug) {
req.payload.logger.info('Pre-scanning all columns before streaming')
@@ -122,7 +133,7 @@ export const createExport = async (args: CreateExportArgs) => {
const result = await payload.find({ ...findArgs, page: scanPage })
result.docs.forEach((doc) => {
const flat = flattenObject({ doc, fields, toCSVFunctions })
const flat = filterDisabled(flattenObject({ doc, fields, toCSVFunctions }))
Object.keys(flat).forEach((key) => {
if (!allColumnsSet.has(key)) {
allColumnsSet.add(key)
@@ -156,7 +167,9 @@ export const createExport = async (args: CreateExportArgs) => {
return
}
const batchRows = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
const batchRows = result.docs.map((doc) =>
filterDisabled(flattenObject({ doc, fields, toCSVFunctions })),
)
const paddedRows = batchRows.map((row) => {
const fullRow: Record<string, unknown> = {}
@@ -217,7 +230,9 @@ export const createExport = async (args: CreateExportArgs) => {
}
if (isCSV) {
const batchRows = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
const batchRows = result.docs.map((doc) =>
filterDisabled(flattenObject({ doc, fields, toCSVFunctions })),
)
// Track discovered column keys
batchRows.forEach((row) => {

View File

@@ -1,6 +1,6 @@
import type { Config, FlattenedField } from 'payload'
import { addDataAndFileToRequest, deepMergeSimple } from 'payload'
import { addDataAndFileToRequest, deepMergeSimple, flattenTopLevelFields } from 'payload'
import type { PluginDefaultTranslationsObject } from './translations/types.js'
import type { ImportExportPluginConfig, ToCSVFunction } from './types.js'
@@ -58,6 +58,26 @@ export const importExportPlugin =
},
path: '@payloadcms/plugin-import-export/rsc#ExportListMenuItem',
})
// Flatten top-level fields to expose nested fields for export config
const flattenedFields = flattenTopLevelFields(collection.fields, {
moveSubFieldsToTop: true,
})
// Find fields explicitly marked as disabled for import/export
const disabledFieldAccessors = flattenedFields
.filter((field) => field.custom?.['plugin-import-export']?.disabled)
.map((field) => field.accessor || field.name)
// Store disabled field accessors in the admin config for use in the UI
collection.admin.custom = {
...(collection.admin.custom || {}),
'plugin-import-export': {
...(collection.admin.custom?.['plugin-import-export'] || {}),
disabledFields: disabledFieldAccessors,
},
}
collection.admin.components = components
})
@@ -161,6 +181,14 @@ export const importExportPlugin =
declare module 'payload' {
export interface FieldCustom {
'plugin-import-export'?: {
/**
* When `true` the field is **completely excluded** from the import-export plugin:
* - It will not appear in the “Fields to export” selector.
* - It is hidden from the preview list when no specific fields are chosen.
* - Its data is omitted from the final CSV / JSON export.
* @default false
*/
disabled?: boolean
toCSV?: ToCSVFunction
}
}