### What?
Fixes a sync issue between the "Fields to Export" `<ReactSelect />`
dropdown and the underlying form state in the import-export plugin.
### Why?
Previously, the dropdown displayed outdated selections until an extra
click occurred. This was caused by an unnecessary `useState`
(`displayedValue`) that fell out of sync with the `useField` form value.
### How?
- Removed the separate `displayedValue` state
- Derived the selected values directly from the form field value using
inline mapping
### What?
Fixes an issue where only the fields from the first batch of documents
were used to generate CSV column headers during streaming exports.
### Why?
Previously, columns were determined during the first streaming batch. If
a field appeared only in later documents, it was omitted from the CSV
entirely — leading to incomplete exports when fields were sparsely
populated across the dataset.
### How?
- Adds a **pre-scan step** before streaming begins to collect all column
keys across all pages
- Uses this superset of keys to define the final CSV header
- Ensures every row is padded to match the full column set
This matches the behavior of non-streamed exports and guarantees that
the streamed CSV output includes all relevant fields, regardless of when
they appear in pagination.
### What?
Ensure the export preview table includes all field keys as columns, even
if those fields are not populated in any of the returned documents.
### Why?
Previously, if none of the documents in the preview result had a value
for a given field, that column would be missing entirely from the
preview table.
### How?
- Introduced a `getFlattenedFieldKeys` utility that recursively extracts
all missing flattened field accessors from the collection’s config that
are undefined
- Updates the preview UI logic to build columns from all flattened keys,
not just the first document
This PR consists of two separate changes. One change cannot pass CI
without the other, so both are included in this single PR.
## CI - ensure types are generated
Our website template is currently failing to build due to a type error.
This error was introduced by a change in our generated types.
Our CI did not catch this issue because it wasn't generating types /
import map before attempting to build the templates. This PR updates the
CI to generate types first.
It also updates some CI step names for improved clarity.
## Fix: type error

This fixes the type error by ensuring we consistently use the _same_
generated `TypedUser` object within payload, instead of `BaseUser`.
Previously, we sometimes used the generated-types user and sometimes the
base user, which was causing type conflicts depending on what the
generated user type was.
It also deprecates the `User` type (which was essentially just
`BaseUser`), as consumers should use `TypedUser` instead. `TypedUser`
will automatically fall back to `BaseUser` if no generated types exists,
but will accept passing it a generated-types User.
Without this change, additional properties added to the user via
generated-types may cause the user object to not be accepted by
functions that only accept a `User` instead of a `TypedUser`, which is
what failed here.
## Templates: re-generate templates to update generated types
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210668927737258
Needed for #12860.
The new live preview pattern requires collection-level preferences, a
pattern that does not yet exist.
Instead of creating a new record for these types of preferences, we can
simply reuse `<collectionSlug>-list` under a more general key:
`collection-<slug>`. This way other relevant properties can be attached
in the future that might not specifically apply to the list view.
This will also match the conventions already estalished by
document-level preferences in `collection-<slug>-<id>` and
`global-<slug>`.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210628212784050
### What
This PR updates the import-export plugin's `<Preview />` component to
render table columns and rows using the same logic as the CSV export.
Key changes:
- Adds a new `/api/preview-data` custom REST endpoint that:
- Accepts filters (`fields`, `where`, `sort`, `draft`, `limit`)
- Uses `getCustomFieldFunctions` and `flattenObject` to transform
documents
- Returns deeply flattened rows identical to the CSV export
- Refactors the <Preview /> component to:
- POST preview config to the new endpoint instead of querying the
collection directly
- Match column ordering and flattening logic with the `createExport`
function
- Ensures consistency across CSV downloads and in-admin previews
-Adds JSON preview
This ensures preview results now exactly match exported CSV content,
including support for custom field transformers and polymorphic fields.
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This PR fixes an issue in the export logic where CSV downloads would
include duplicate rows and repeated column headers across paginated
batches.
Key changes:
- Ensured `page` is incremented correctly after each `payload.find` call
- Tracked and wrote CSV column headers only once for the first page
- Prevented row duplication by removing unused `result` initialization
and using isolated `page` tracking
- Streamlined both download and non-download logic for consistent batch
processing
This resolves incorrect row counts and header duplication in large CSV
exports.
### What?
Fixes CSV export support for polymorphic relationship and upload fields.
### Why?
Polymorphic fields in Payload use a `{ relationTo, value }` structure.
The previous implementation incorrectly accessed `.id` directly on the
top-level object, which caused issues depending on query depth or data
shape. This led to missing or invalid values in exported CSVs.
### How?
- Updated getCustomFieldFunctions to safely access relationTo and
value.id from polymorphic fields
- Ensured `hasMany` polymorphic fields export each related ID and
relationTo as separate CSV columns
### What?
Ensure fields using a custom `toCSV` function that return `undefined`
are excluded from the exported CSV.
### Why?
Previously, when a `toCSV` function returned `undefined`, the field key
would still be added to the export row. This caused the column to appear
in the CSV output with an empty string value (`""`), leading to
unexpected results and failed assertions in tests expecting the field to
be truly omitted.
### How?
Updated the `flattenObject` utility to:
- Check if the value returned by a `toCSV` function is `undefined`
- Only assign the value to the export row if it is explicitly defined
- Applied this logic in all relevant paths (arrays, objects, primitives)
This change ensures that fields are only included in the CSV when a
meaningful value is returned.
The custom save button showing on export enabled collections in the edit
view. Instead it was meant to only appear in the export collection. This
makes it so it only appears in the export drawer.
Type declaration extending `custom.['plugin-import-export']` was
incorrectly named `toCSVFunction` instead of `toCSV`. This changes the
type to match the correct property name `toCSV`.
When making an export of a collection, and no fields are selected then
you get am empty CSV. The intended behavior is that all data is exported
by default.
This fixes the issue that from the admin UI, when the fields selector
the resulting CSV has no columns.
### What?
In a project that has `jobs` configured and the import/export plugin
will error on start:
`payload-jobs validation failed: taskSlug: createCollectionExport is not
a valid enum value for path taskSlug.`
### Why?
The plugin was not properly extending the jobs configuration.
### How?
Properly extend existing config.jobs to add the `createCollectionExport`
task.
Fixes #
This makes it possible to add custom logic into how we map the document
data into the CSV data on a field-by-field basis.
- Allow custom data transformation to be added to
`custom.['plugin-import-export'].toCSV inside the field config
- Add type declaration to FieldCustom to improve types
- Export with `depth: 1`
Example:
```ts
{
name: 'customRelationship',
type: 'relationship',
relationTo: 'users',
custom: {
'plugin-import-export': {
toCSV: ({ value, columnName, row, siblingDoc, doc }) => {
row[`${columnName}_id`] = value.id
row[`${columnName}_email`] = value.email
},
},
},
},
```
The i18n namespace `movingFromFolder`'s second dynamic variable name
should be `{{fromFolder}}`, but lots of locales use `{{folderName}}`, so
it will fail to get the value.
8199a7d32a/packages/ui/src/elements/FolderView/Drawers/MoveToFolder/index.tsx (L360-L363)
There is also some optimization for Folder related translation and
generic terms of zh.ts included inside.
Converts all text and field labels into variables that can be
translated. Also generated the translations for them
So now the UI here is internationalised

I've also moved some of the generic labels into the core package since
those could be re-used elsewhere
### What?
The order of fields, when specified for the create export function was
not used for constructing the data. Now the fields order will be used.
### Why?
This is important to building CSV data for consumption in other systems.
### How?
Adds logic to handle ordering the field values assigned to the export
data prior to building the CSV.
When server rendering custom components within form state, those
components receive a path that is correct at render time, but
potentially stale after manipulating array and blocks rows. This causes
the field to briefly render incorrect values while the form state
request is in flight.
The reason for this is that paths are passed as a prop statically into
those components. Then when we manipulate rows, form state is modified,
potentially changing field paths. The component's `path` prop, however,
hasn't changed. This means it temporarily points to the wrong field in
form state, rendering the data of another row until the server responds
with a freshly rendered component.
This is not an issue with default Payload fields as they are rendered on
the client and can be passed dynamic props.
This is only an issue within custom server components, including rich
text fields which are treated as custom components. Since they are
rendered on the server and passed to the client, props are inaccessible
after render.
The fix for this is to provide paths dynamically through context. This
way as we make changes to form state, there is a mechanism in which
server components can receive the updated path without waiting on its
props to update.
### What?
- GraphQL was broken because of an error with the enum for the drafts
input which cannot be 'true'.
- Selecting Draft was not doing anything as it wasn't being passed
through to the find arguments.
### Why?
This was causing any graphql calls to error.
### How?
- Changed draft options to Yes/No instead of True/False
- Correctly pass the drafts arg to `draft`
Fixes #
### What?
The import-export preview UI component does not handle localized fields
and crash the UI when they are used. This fixes that issue.
### Why?
We were not properly handling the label translated object notation that
field.label can have.
### How?
Now we call `getTranslation` with the field label to handle language
keyed labels.
Fixes # https://github.com/payloadcms/payload/issues/11668
Adds new plugin-import-export initial version.
Allows for direct download and creation of downloadable collection data
stored to a json or csv uses the access control of the user creating the
request to make the file.
config options:
```ts
/**
* Collections to include the Import/Export controls in
* Defaults to all collections
*/
collections?: string[]
/**
* Enable to force the export to run synchronously
*/
disableJobsQueue?: boolean
/**
* This function takes the default export collection configured in the plugin and allows you to override it by modifying and returning it
* @param collection
* @returns collection
*/
overrideExportCollection?: (collection: CollectionOverride) => CollectionOverride
// payload.config.ts:
plugins: [
importExportPlugin({
collections: ['pages', 'users'],
overrideExportCollection: (collection) => {
collection.admin.group = 'System'
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return collection
},
disableJobsQueue: true,
}),
],
```
---------
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Kendell Joseph <kendelljoseph@gmail.com>