strapi-plugin-data-excel-exporter logo

strapi-plugin-data-excel-exporter

By Vinod KV

Export Strapi v5 collection entries to .xlsx files directly from the admin panel.

Latest version

v1.2.7

released November 6, 2026

npm install strapi-plugin-data-excel-exporter

strapi-plugin-data-excel-exporter

npm version License: MIT

Export 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.

Requirements

  • Strapi ^5.0.0
  • Node >=20.0.0 <=24.x.x
  • React 18

Installation

yarn add strapi-plugin-data-excel-exporter
# or
npm install strapi-plugin-data-excel-exporter

Enable the plugin in config/plugins.ts (or .js):

export default () => ({
  'data-exporter': {
    enabled: true,
  },
});

Then rebuild and start the admin:

yarn build
yarn develop

Permissions

Bulk-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:

  1. Open Settings → Administration Panel → Roles.
  2. Pick a role (e.g. Editor).
  3. Under the relevant content type, tick Export collections.
  4. Save.

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.

Usage

  1. Open any collection in the Content Manager.
  2. Tick the checkboxes for the rows you want to export.
  3. Click Export in the bulk-action bar that appears above the table.
  4. The .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).

Configuration

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,
    },
  },
});
KeyDefaultDescription
maxRows50000Hard cap on entries per export. Exports above this fail with HTTP 413.
pageSize500Internal pagination size used while streaming. Auto-clamped to maxRows.
flattenDepth3How 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).
sanitizeFormulastrueIf true, prepend ' to cell values starting with =/+/-/@. See Security below.

How an export happens

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>.xlsx

The 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).

What gets exported

Strapi attributeExported 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 componentsOne column, JSON-encoded array
Dynamic zonesOne column, JSON-encoded array
Media (single)URL string
Relations (single)The related entry's relationDisplayField (default: id)
Relations (many)Comma-joined relationDisplayField values
PasswordAlways excluded

Security: formula injection

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.

Limits

  • Row cap: 50,000 by default. Tune via maxRows. Exceeding it fails the export with HTTP 413 (ROW_LIMIT_EXCEEDED) before any bytes are written.
  • Request timeout: For exports near the cap (~30–60s on commodity hardware with wide schemas), make sure your Strapi/Koa requestTimeout is at least 120s. Adjust in config/server.ts:
    export default ({ env }) => ({
      // …
      requestTimeout: 120_000,
    });

FAQ

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.

Out of scope (v1)

  • REST or CLI export triggers
  • Bulk-action on selected rows
  • Async/job-queue mode
  • Multi-sheet "full fidelity" workbooks
  • Per-relation relationDisplayField overrides
  • Strapi v4 compatibility

License

MIT — see LICENSE.

Submit your content

Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.

Submit
Submit your content