A customer data export format can contain every row and still be useless. Dates arrive without time zones, status codes have no definitions, deleted records disappear without explanation, and related objects lose the identifiers that connected them.

A good export preserves the meaning of the data as well as the values. That means designing the package as a product surface with a stable schema, clear definitions and tests against real import work.

Start with the job the export must do

“Download my data” can describe several different jobs:

  • a person wants to read a copy of their account history;
  • a customer wants to move records to another service;
  • an administrator needs an archive for audit or continuity;
  • a developer needs a repeatable feed that another system can process.

The required scope and format change with the job. The UK Information Commissioner’s Office says the portability right covers personal data a person provided, including some data observed from their use of a service. Inferred profiles created by the organisation sit outside that portability scope, although they may still be relevant to a separate access request. The ICO’s portability guidance is a useful boundary for legal requests, but a product can choose to offer a broader export when it can do so safely.

Write the scope down before choosing a file type. Name the account data, activity, uploaded files, preferences, messages and administrative events that are included. Name what is excluded and why. The same discipline applies when setting retention rules for customer-support data: a field should have an owner, a purpose and an explicit lifecycle.

Pick a format that can carry the structure

The ICO describes a portable format as structured, commonly used and machine-readable. CSV, XML and JSON can all meet that test, but they preserve different kinds of structure.

CSV works well for one flat table. It becomes awkward when one order has many items, one conversation has many messages, or a field can contain a list. Splitting those objects into several CSV files can work if every row carries a stable identifier and the package explains how the tables join.

JSON can preserve nested objects, arrays, null values and explicit types. That often makes it a better fit for product data with relationships, but a large JSON document can be difficult for a person to inspect. A practical package may include JSON for machines, CSV views for common tables and a short human-readable guide.

Files such as photos, invoices and documents should normally remain separate binary files. Put them beside a manifest that records their path, media type, size, checksum and the record that owns them. Embedding every file as base64 inside JSON increases the archive size and makes recovery harder.

The schema is part of the export

A column called status has little value if the recipient cannot tell whether 3 means paid, cancelled or awaiting review. A timestamp without a time zone can move an event to another day. An empty string can mean “not provided,” “not applicable” or “redacted.”

Ship a small schema document with the package. For each field, record:

  • the field name and plain-language meaning;
  • its data type and whether it can be null;
  • units, currency and time-zone rules;
  • permitted status values;
  • whether the field is supplied, observed or derived;
  • the identifier of the object it refers to;
  • any redaction or omission rule.

Version the schema independently from the application release. An export reader should be able to see schema_version: 2 and know which contract applies. When a meaning changes, add a version rather than silently reusing the old field.

The Data Transfer Project uses common data models and service-specific adapters so that services can translate their own representation into a shared one. The project also warns that its code is still under active development. The useful design lesson is the separation: map local fields into a documented transfer model, and test both sides of that mapping.

Keep identity and relationships intact

Exported records need identifiers that remain stable for the life of the object. Display names, email addresses and titles can change. They are poor join keys.

If an invoice belongs to an order and an order belongs to a customer, keep those links explicit:

  • invoice.order_id
  • order.customer_id
  • attachment.owner_type and attachment.owner_id

Avoid exporting internal database keys when they reveal implementation details or could be used against a live API. A dedicated export identifier can preserve relationships without exposing production IDs. Document whether identifiers are stable across repeated exports, because that determines whether a recipient can compare two packages safely.

Relationships also need direction and cardinality. “Member” may mean a user belongs to one workspace, many workspaces, or a historical workspace that has since closed. The schema should say which is true.

Preserve time, money and absence

Time and money expose weak exports quickly.

Use ISO 8601 timestamps with an explicit offset or UTC marker. Keep the original local time zone where it matters to a human event, such as an appointment. For money, export an integer minor-unit amount with a separate ISO currency code, or a decimal string with a documented scale. Do not leave a bare 19.99 for another system to interpret.

Treat absence as data. Keep null, an omitted field and an empty collection distinct when they carry different meanings. If a record was deleted, consider an export tombstone with its stable identifier and deletion time. Otherwise a later import may recreate it or mistake it for a record that never existed.

Protect other people inside the archive

A customer’s records can contain names, messages and files belonging to other people. The ICO says an organisation must consider whether a portability transfer would adversely affect third-party rights, especially when sending data directly to another controller.

Build that review into the export model. A message export may include the requester’s copy of a conversation while redacting another person’s private profile fields. A joint account may require agreement from more than one person. The package should mark redacted fields rather than shifting columns or producing malformed records.

Delivery matters too. Generate archives on demand, encrypt them in transit, give links a short lifetime and verify the destination for direct transfers. The ICO places responsibility on the sender for secure transmission to the correct destination.

Test the package as an importer would

Counting rows is not enough. A release test should:

  1. create a fixture account with nested records, attachments, Unicode text, several time zones, nulls and deleted items;
  2. generate the export;
  3. validate every file against the declared schema;
  4. verify checksums and referential links;
  5. import it into a clean test model;
  6. compare the reconstructed meaning with the fixture.

Also test a second export after records change. Stable identifiers should let the test distinguish a new object, an edited object and a deletion.

Track failures by schema version and field, not only by request. If a new application release drops a relationship or changes a unit, the export test should fail before customers receive the archive.

A compact export contract

A useful package can be simple:

  • manifest.json for export time, account, schema version, files and checksums;
  • schema.json for field definitions and relationships;
  • data/ for JSON or CSV records;
  • files/ for uploaded media and documents;
  • README.txt for scope, omissions, security notes and import guidance.

The archive is successful when another person or system can reconstruct what the records mean. File completeness is only the first check.

Reporting & Sources