By Vinod KV
Export Strapi v5 collection entries to .xlsx files directly from the admin panel.
released November 6, 2026
npm install strapi-plugin-data-excel-exporterExport Strapi v5 collection entries to .xlsx files directly from the admin panel.
Select one or more entries in any Content Manager list view; an Export action appears alongside Delete/Publish. Click it and the selected entries stream down to your browser as an Excel workbook with every flattenable field included.
^5.0.0>=20.0.0 <=24.x.xyarn add strapi-plugin-data-excel-exporter
# or
npm install strapi-plugin-data-excel-exporterEnable the plugin in config/plugins.ts (or .js):
export default () => ({
'data-exporter': {
enabled: true,
},
});Then rebuild and start the admin:
yarn build
yarn developBulk-exporting a collection is intentionally separate from reading individual entries. After installing the plugin, grant the Export collections action to roles that should have it:
Users without this permission will see a 403 Forbidden toast when they try to export.
Super-admins always have access — they don't need explicit permission.
.xlsx downloads automatically with every flattenable field included for the selected entries.To export the entire collection, use the "select all" checkbox in the table header (Strapi-managed, paginates as expected).
All keys are optional; defaults are shown.
// config/plugins.ts
export default () => ({
'data-exporter': {
enabled: true,
config: {
maxRows: 50_000,
pageSize: 500,
flattenDepth: 3,
relationDisplayField: 'id',
excludeAttributes: ['password', 'resetPasswordToken'],
filenameTemplate: '{collection}-{date}.xlsx',
sanitizeFormulas: true,
},
},
});| Key | Default | Description |
|---|---|---|
maxRows | 50000 | Hard cap on entries per export. Exports above this fail with HTTP 413. |
pageSize | 500 | Internal pagination size used while streaming. Auto-clamped to maxRows. |
flattenDepth | 3 | How many levels of nested components to flatten into dotted columns. Deeper paths are collapsed to a JSON column. Range [1, 10]. |
relationDisplayField | 'id' | Which field of a related entry to render in cells. Many-relations are joined by comma. |
excludeAttributes | ['password', 'resetPasswordToken'] | Attribute names that are never exported, regardless of selection. |
filenameTemplate | '{collection}-{date}.xlsx' | {collection} → info.singularName. {date} → ISO-8601 basic UTC (e.g. 20260427T143015Z). |
sanitizeFormulas | true | If true, prepend ' to cell values starting with =/+/-/@. See Security below. |
admin click → POST /data-exporter/export
{ uid, query: { filters: { documentId: { $in: [...] } } } }
→ isAuthenticatedAdmin policy
→ has-export-permission policy (RBAC check on uid)
→ controller validates body
→ exporter.run
├─ describe(uid) → resolve flattenable fields
├─ documentService.count(query) → check vs maxRows
├─ open xlsx stream-writer
└─ paginate documentService.findMany (page=N, pageSize)
└─ per entry: flatten → xlsx-writer.appendRow
├─ commit
└─ return Readable
→ controller pipes Readable to response
→ browser downloads <collection>-<ISO>.xlsxThe export streams — entries flow from the database, through pagination, into xlsx rows, and out to the browser without ever buffering the full file in memory.
The endpoint also accepts an optional fields: string[] to restrict columns; the bulk-action UI omits it (sends every flattenable field).
| Strapi attribute | Exported as |
|---|---|
| Scalars (string, integer, boolean, date, datetime, JSON, enumeration, …) | One column each |
| Components (single, nested) | Flattened into dotted columns (address.country.name) up to flattenDepth |
| Repeatable components | One column, JSON-encoded array |
| Dynamic zones | One column, JSON-encoded array |
| Media (single) | URL string |
| Relations (single) | The related entry's relationDisplayField (default: id) |
| Relations (many) | Comma-joined relationDisplayField values |
| Password | Always excluded |
By default (sanitizeFormulas: true), any cell value starting with =, +, -, or @ gets a leading apostrophe (') prepended. This neutralizes the well-known CSV/xlsx formula-injection vector — without it, a user who can write into a content-type field could craft a value like =HYPERLINK(...) that executes when another admin opens the exported file in Excel.
Disable only if your data is fully trusted and downstream consumers handle escaping themselves.
maxRows. Exceeding it fails the export with HTTP 413 (ROW_LIMIT_EXCEEDED) before any bytes are written.requestTimeout is at least 120s. Adjust in config/server.ts:
export default ({ env }) => ({
// …
requestTimeout: 120_000,
});Q: Can I export every locale at once? No — Strapi's bulk-selection lives within the current locale. Switch the locale picker to export a different language, or repeat per locale.
Q: Can I customize the relation display per content type?
Not in v1. relationDisplayField is global. Per-relation overrides are tracked for v2.
Q: Can I pick which columns to include?
Not from the bulk action — it always exports every flattenable field. The HTTP endpoint (POST /data-exporter/export) accepts an optional fields: string[] if you want to drive it programmatically.
Q: Why is the file empty / why does my browser open the response inline?
Some networks rewrite Content-Disposition headers. If filenames look generic (export.xlsx) or the file streams to the browser tab instead of downloading, check that no proxy is stripping headers between Strapi and the client.
Q: My collection has 200k rows. How do I export it?
Increase maxRows and requestTimeout (and your Node memory if needed). For collections that large, an async/job-queue mode is on the roadmap for v2.
relationDisplayField overridesMIT — see LICENSE.
Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.
Submit
