# Customer data exports: what should someone be able to take from your SaaS?

> Define a usable customer export with records, relationships and actual attachments. A worked example shows why matching row counts can still hide missing files.

Source: https://saascode.ai/inside/customer-data-export-for-saas · Published: 2026-10-16 · Section: use-case

---
A useful SaaS customer data export lets someone recover the work they put into the service: records, meaningful relationships, relevant comments and the actual files they are entitled to take. A download containing the right number of rows can still fail that job. The practical test is whether the customer can understand and reopen the exported work without depending on the original application.

For a SaaS operator, that means defining the export's scope, packaging its contents clearly and checking the result from the recipient's side. Start with one customer workflow. Then make the omissions as explicit as the included data.

Consider a fictional design studio with two projects, North and South. Each has tasks and discussion, and each project includes an attachment called `brief.txt`. The filenames match, but the contents and the tasks they belong to are different. That small example exposes several ways an apparently successful export can lose useful work.

## Decide what the customer needs to do afterward

An export can serve different purposes. Someone may want an archive they can read, a handover to a colleague, a feed for analysis or material to migrate into another application. Ask which outcome you are promising before choosing a file format.

A readable archive needs enough context for a person to follow the work. A migration package also needs stable identifiers, documented fields and relationships a destination can map. A charting export might intentionally contain only a selected table. All three can be useful if their scope is clear.

An operator's database backup is a separate deliverable. It may contain application internals, multiple customers and credentials or settings that do not belong in a customer download. It may also depend on the original application to make sense of the records. Do not expose a full database dump as a shortcut to a customer export.

Likewise, exporting data does not demonstrate that another service can import it. Even within one vendor, export and restoration differ: [Notion's export guidance](https://www.notion.com/help/export-your-content) describes several export formats and notes that uploading exported content does not instantly recreate the workspace. Its guidance also explains that pages the exporter cannot access may be excluded. Those are useful reasons to inspect an export's contract, not assume that a download means “everything restored elsewhere.”

If the customer is moving into a new application, the [customer migration guide](/inside/migrate-customers-to-your-saas) covers the destination's field mappings and acceptance. Here, the question is what leaves your service and whether it remains understandable.

## Package the work, its relationships and its limits

For the fictional studio, a proposed package inventory looks like this:

| Part | What it contains | What the recipient can establish |
|---|---|---|
| Projects | Project IDs and names | Which body of work a task belongs to |
| Tasks | Task IDs, project IDs, references and current fields | Which task is which, including references with leading zeros |
| Comments | Comment IDs, task IDs and text | Which discussion belongs to which task |
| Attachment index | File IDs, task IDs, original filenames and relative paths | Where each file belongs and where its bytes are stored |
| Files | Actual attachment contents | Whether the recipient can open them without a live source link |
| Manifest and reading notes | Scope, export time, format version, counts and field meanings | What was included, how to interpret it and what was excluded |

This is an example inventory, not a requirement that every SaaS export these exact entities. A booking application or reporting service will have different work to preserve. Start from the customer's task and inspect the product's actual records.

Separate an empty value from a meaningful zero. In our example, an estimated duration of `0` means zero hours, while `null` means the estimate is unknown. The notes explain that distinction. A reference such as `0017` is an identifier, not an instruction to convert it into the number 17.

Make omissions visible. An export of current tasks might exclude historical revisions, deleted items, automations and account permissions. A customer should not have to discover those exclusions after closing their account. If you include author identity or timestamps, define their meaning and handle personal information according to the agreed scope and applicable requirements; this example is not a legal portability assessment.

## Preserve files with identity, not just their names

The two `brief.txt` files cannot safely occupy the same destination path. Nor should the customer have to guess which project owns which file after the download.

Our example assigns the first attachment `file-a` and the second `file-b`. Their exported paths are `files/file-a/brief.txt` and `files/file-b/brief.txt`. An attachment index retains each original filename and joins its file ID to its task ID. The task then joins to its project.

![The North task points to files/file-a/brief.txt and the South task points to files/file-b/brief.txt: the original filenames match, but IDs, paths, parents and contents differ.](https://hpcxvndsvdynlbperiah.supabase.co/storage/v1/object/public/content/own-blog/customer-data-export-for-saas/same-name-different-files-58c4931b.svg)

*Original diagram of the synthetic fixture. Stable file folders preserve two different attachments called brief.txt and their task relationships. It does not document an existing product feature.*

A cell containing an attachment URL is not necessarily the file. It might require the old account, expire or point to something the exporter cannot retrieve. If the promise includes attachments, include their bytes or clearly report which ones are unavailable and why. Do not silently count a filename as a successful file transfer.

Keep the package navigable for people as well as software. Retaining the original filename helps someone recognize a document; the unique path prevents collisions. A short index can show the project, task and relative file path together without requiring the recipient to understand your internal database schema.

## Reopen the package and compare it with the source

We executed a small local export exercise with synthetic data. It wrote UTF-8 JSON files for two projects, three tasks, three comments and two attachment records, plus the two actual text attachments. We then read those files back from disk and compared the recovered records with the source fixture.

The records include a leading-zero reference, accented text, a comma, quoted words and a line break, as well as zero and null values. The JSON readback preserved those values exactly. We checked eight parent references: three tasks to projects, three comments to tasks and two attachments to tasks. We also compared each recovered attachment's bytes with its recorded SHA-256 hash.

For contrast, we constructed a second package with identical metadata and no attachment files. Its record counts and parent references still matched. Its file-content checks failed.

| Check | Complete fixture | Metadata-only fixture |
|---|---|---|
| Projects / tasks / comments / attachment records | 2 / 3 / 3 / 2 | 2 / 3 / 3 / 2 |
| Records equal the source values | Yes | Yes |
| Valid parent references | 8 of 8 | 8 of 8 |
| Attachment contents recovered and matched | 2 of 2 | 0 of 2 |
| Meets this example's package requirements | Yes | No |

![Both executed fixture packages retain identical records and eight valid parent references, but only the complete package has two of two verified attachment contents; the metadata-only package has zero.](https://hpcxvndsvdynlbperiah.supabase.co/storage/v1/object/public/content/own-blog/customer-data-export-for-saas/export-reopen-results-08c218c5.svg)

*Measured results from the local JSON-and-text-file exercise. The missing-file variant was deliberately incomplete. Hash comparisons check byte equality, not authenticity or security.*

This is a filesystem exercise, not a test of a SaaSCode product's exporter. It does not test a CSV parser, ZIP packaging, authorization, simultaneous edits, large exports or importing into another application. Its narrower finding is useful: the correct record count and intact parent IDs did not establish that the attachment contents were present.

For a real product, add a customer-facing readback: open a representative project, locate its task, read the discussion and open the intended attachment from the downloaded package. Include one awkward record and one known exclusion. A machine comparison and a human reading check answer different parts of the acceptance question.

## Choose formats for the next reader

CSV is useful for tabular data when the receiving program understands its conventions. It does not describe your relationships or include a folder of attachments by itself. Multiple related CSVs can be a valid design if their IDs, joins and field meanings are documented.

[RFC 4180](https://www.rfc-editor.org/rfc/rfc4180.txt) documents common CSV conventions for commas, quotation marks and line breaks, and notes differences among implementations. Check the tools your customers actually use. A file extension alone is not evidence that dates, encoding and identifiers will survive their next step.

JSON can preserve explicit types and nested structures, as it does in our exercise, but many customers will not want to read it directly. A human-readable index or archive can accompany machine-readable records. The extra view should help explain the same data, not introduce conflicting values.

Spreadsheet viewing deserves a separate check. [OWASP's CSV Injection guidance](https://community.owasp.org/attacks/CSV_Injection) describes how untrusted content can be interpreted as formulas and why no single sanitization strategy suits every spreadsheet and downstream consumer. Ordinary CSV quoting does not settle that problem. Define whether an export is intended for machine import or spreadsheet viewing, document any transformations and test the chosen handling in the intended consumer. Our JSON exercise does not validate spreadsheet safety.

## Define who can export, and which moment the package represents

The export must have an authorized scope. A customer selecting a workspace name in a form is not, by itself, proof they can receive every record or file associated with it. Establish permissions on the server and apply the intended boundary to attachments as well as rows.

Test that boundary separately from package completeness. Can the intended role export the agreed work? Does another workspace's content stay out? What happens if access changes while an export job is running? Record the behavior instead of assuming that a hidden button is sufficient protection.

Time matters too. If records change while the job reads them, a package can combine states that never existed together. Define the export's consistency approach and describe the effective snapshot or extraction window. For a small, assisted transfer, an agreed pause in edits may be practical; a larger service needs an appropriate implementation for its workload. An export timestamp alone does not prove a consistent snapshot.

A failed file retrieval should appear as an exception, with a route to retry or get assistance. If you provide an ongoing data connection rather than a one-time download, treat it as a separate operating promise. The [guide to connecting two SaaS products](/inside/connect-two-saas-products) discusses repeated deliveries and uncertain outcomes that a continuing integration needs to handle.

## Make the promise match the application you operate

A useful export description is specific: “Download current projects, tasks, comments and available attachments, with relationships and a list of exclusions.” Only make that promise after the implementation and checks support it. Avoid an unqualified “all your data” when revisions, permissions or files are absent.

Before launching a service from a finished application, inspect its export screen, documentation and sample output. Try the ordinary customer role and read the resulting package. If the required export does not exist, determine whether a configuration change, a supported extension or development work is needed. Source access gives you a way to adapt the product; it does not mean every export capability is already included.

SaaSCode's [catalogue of finished applications](/products) provides software you can operate and customize for your business. Match the selected product to the work your customers need to keep, alongside the other steps in the [source-code launch guide](/inside/launch-a-saas-from-source-code). A credible handover begins with a modest, testable promise: the customer can identify their work, follow its relationships and reopen the files you said were included.
