Skip to content

feat: export collection items to CSV #348

Description

@tristan-mouchet

Feature request

Add the ability to export a collection's items to a CSV file from the CMS, mirroring the behavior that existed in the legacy Ycode app. We already support importing collections from CSV — this adds the missing export side.

Problem

Today collections can only be imported (CSVImportDialog), never exported. There's no way for a user to pull their CMS data out as a CSV — for backup, migration, editing in a spreadsheet, or re-importing elsewhere. The legacy app had this and it's a frequently expected CMS capability.

How it worked in the legacy app

For reference, the legacy (Laravel/Vue) implementation:

  • An Export button in the collection data toolbar (DataType.vue), labelled Export {n} where n reflects the number of selected items.
  • Button is disabled when the collection has no items, and shows a loading spinner while the export runs.
  • It exported either all items or, if rows were checked, only the selected items.
  • Backend (CollectionController::exportData) resolved the full collection (no pagination), built the CSV, stored it temporarily on disk, scheduled deletion after 10 minutes, and returned a { name, url } for download (downloadExport).
  • CSV columns used the field display names as headers.
  • Built-in audit columns were excluded: Updated date, Created by, Updated by, Status (Created date was kept).
  • Value flattening per field type:
    • Reference → the referenced item's name field value
    • Multi reference → name field values joined by ;
    • Link → the URL string

Proposed solution

Implement the same flow natively in the Next.js app, reusing what we already have:

  • Add an Export action in the CMS collection toolbar/UI (app/(builder)/ycode/components/CMS.tsx), with optional "export selected vs. all" behavior based on checked items.
  • Reuse the existing CSV helpers in lib/csv-utils.tsrowsToCSV() and downloadCSV() — already used by the redirects settings export, so no new CSV-writing code is needed.
  • Build rows from collection fields + items, applying the same value-flattening rules (reference → name, multi-reference → joined names, link → URL, rich_text → HTML or plain text).
  • Exclude built-in audit columns (Updated date, Created by, Updated by, Status) to match legacy output.
  • Name the file after the collection (e.g. blog.csv).

Since the export can be generated client-side from already-loaded collection data (or via a thin repository/route fetch for the full set), we likely don't need the legacy's temp-file/storage dance — downloadCSV() triggers the browser download directly.

Open questions

  • Export all items always, or respect current filters/sorting and selection (selected-only)?
  • For large collections, do we fetch the full set server-side (new route/repository call) or rely on already-loaded paginated data?
  • How should rich_text be serialized — HTML, plain text, or raw TipTap JSON? (Import accepts HTML, so HTML round-trips best.)
  • Should asset fields (image/video/etc.) export the public URL?

Context

We already ship the import counterpart (CSVImportDialog, collectionImportRepository, import API routes). This closes the loop. Building blocks (rowsToCSV, downloadCSV, collection field metadata) already exist in the codebase.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions