What Should a CSV Export from a Small Business Web App Include?
A CSV export should include whatever you and your developer agree in writing, and the agreement should cover seven things: which rows are exported, which columns appear in which order, how each kind of value is written, how the file handles spreadsheet behaviour and text encoding, who may export, what happens to the file after download, and how you will accept the result. If any of those is left to guesswork, the first export tends to surprise someone. This article is proposed commissioning guidance. It describes what you could ask for, not functionality that already exists or is guaranteed in any product.
I work on websites, web apps and mobile apps. The scope below is a starting point for a conversation about your own project, and every item can be removed, changed or extended.
Treat CSV as a habit, not a strict standard
The document most people cite for CSV, RFC 4180, describes itself as informational. It records the format that seems to be followed by most implementations and does not set an Internet standard. It also says that, because no single specification exists, implementations differ considerably (RFC 4180).
So write your own rules into the brief instead of asking for standard CSV. State the delimiter, the line ending, the text encoding, whether there is a header row, and how special characters are quoted. RFC 4180 lists charset as an optional parameter of the text/csv type, which is one more reason to name the encoding yourself (RFC 4180).
Rows: decide what the file contains
A gap between what a user saw on screen and what arrives in the file is an easy way to lose trust. For each export button, decide and label one behaviour:
- All records the user is allowed to see.
- All records matching the current filters.
- Only the current page of results.
- Only the rows the user has selected.
Exporting everything that matches the current filters is often a sensible default, because a page boundary is an accident of the screen. Whichever you choose, show it before the download starts, for example "Export 214 items matching your filters". Also decide whether archived or deleted records are included, and whether the order follows the screen or a fixed rule such as ID ascending. A fixed order makes two exports easier to compare.
Columns: a written list with stable identifiers
List every column, in order, with its meaning. Three decisions matter.
First, include a stable ID for each row, and for any related record export the ID as well as the readable name. Names get corrected, reused and duplicated, while an ID lets a row be matched back to the app later. This is how a CSV can carry relationships between records, by agreed ID columns.
Second, decide on the header row. RFC 4180 describes a header line as optional and says its names correspond to the fields in the file (RFC 4180). Your contract should say that a header is always present, and whether labels follow the screen wording or fixed technical names that will not change when screen text is edited.
Third, say whether the column set is the same for everyone or varies by permission, and keep that rule visible to the user.
Values: dates, time zones, money, units and empty cells
Each value type needs a written rule.
- Dates: one unambiguous format, and a clear difference between a date alone and a date with time.
- Time zones: name the zone for timestamps and say whether the offset is included.
- Money: amount and currency code in separate columns, a stated number of decimal places, and no symbols inside the number.
- Units: put the unit in the column label, such as weight in kilograms, instead of in each cell.
- Empty values: say whether unknown is an empty cell and how that differs from a real zero or an intentional "none". Do not mix several placeholders in one column.
- Awkward text: RFC 4180 describes enclosing fields that contain line breaks, double quotes or commas in double quotes (RFC 4180). Include a test row containing all three.
- Encoding: as a proposed contract, specify UTF-8 and test with accented Latin letters and non Latin scripts. This is a requirement to verify against the programs your team uses, not a claim that every program reads UTF-8 files the same way.
Spreadsheets can change data when they open it
Many users will open the file in a spreadsheet. Microsoft's support documentation says Excel removes leading zeros from numerical text and converts large numbers to scientific notation so that formulas and math operations work (Microsoft Support). The same page describes importing data through Excel's Get & Transform (Power Query) experience and setting individual columns to text during the import (Microsoft Support).
For identifiers such as 00417, long reference numbers or codes that resemble dates, decide the intended user experience. Possible choices are a short instruction beside the export button about importing those columns as text, a separate spreadsheet oriented variant, or both. Test whichever you choose in the spreadsheet programs your team really uses.
Formula risk needs more than quoting
If any exported cell can contain text typed by a customer, supplier or staff member, a spreadsheet may interpret it as a formula. OWASP describes this as CSV injection, or formula injection, which happens when untrusted input is embedded in a CSV file (OWASP).
Quoting is not formula protection. Quoting deals with structure, meaning commas, quotes and line breaks. It does not by itself stop a spreadsheet from evaluating a cell. OWASP also notes that Excel may remove quotes or escape characters when a file is saved and reopened, so some commonly suggested mitigations can stop working, and that no universal sanitization strategy is safe for every spreadsheet program and every downstream consumer (OWASP).
Ask the developer to choose handling for the spreadsheet programs you intend to support, to test it, and to document what it does to the stored values. When writing tests, use sample cells beginning with an equals sign, a plus sign, a minus sign or an at sign, but treat those as a starting set only. The list is not exhaustive. OWASP's page lists further starting characters, including tab, carriage return, line feed and full width variants, and explains why field separators and quote characters also matter. Developers should follow its guidance on controls rather than rely on a short list from a brief.
Some projects may also want a separate raw export for a machine to read, where values must stay exactly as stored. If so, strongly qualify it. It should go only to trusted recipients, with explicit handling instructions, and it must never be presented as safe to open in a spreadsheet. It must follow the same permission checks as every other export and must not become a way around them. Raw does not mean unrestricted. Whether you need one at all is a decision for the brief, not a default.
Permissions and download lifecycle
An export is a copy of data that leaves the app's controls. Specify who may export, which columns each person may include, and whether the export action is recorded. Keep this limited to the export feature, since general access design is a separate topic. Then describe the lifecycle:
- Is the file produced immediately, or prepared in the background when the set is large?
- Where does the user find it, and for how long can it be downloaded again?
- What happens if records change while the file is being prepared?
- What is the file name, for example export type plus date and time?
- What does the user see on failure, or when there are zero rows?
A hypothetical example: equipment inventory
Imagine an invented small business that tracks workshop equipment in a web app. A proposed export called "Equipment inventory" could be specified as follows. This is an illustration, not an existing feature.
- Rows: all active items matching the screen filters, sorted by item ID ascending. Archived items appear only if the filter includes them.
- Columns in order: item_id, item_name, category, location_id, location_name, serial_number, purchase_date, purchase_amount, currency, status, last_checked_at, notes.
- IDs: item_id and location_id are permanent. Names are for reading.
- Dates: purchase_date as a date only, last_checked_at as date and time in one stated zone with offset.
- Money: purchase_amount as a plain number with two decimals, currency in its own column.
- Empty values: an unknown purchase date is an empty cell, and a confirmed zero is written as 0.
- Serial numbers: may start with zeros or be very long, so the brief includes import instructions and a test row.
- Notes: free text, so it receives the tested formula handling, the quoting test row and an accented and non Latin text row.
- Encoding: UTF-8, verified in the programs the team uses.
Acceptance checklist
Copy these into your acceptance criteria. For general guidance on phrasing criteria, see how to write acceptance criteria for a small web app.
- The button states which rows will be exported, and the count matches the file.
- Filters and sort match the written rule.
- The header row is present, and labels and order match the agreed list.
- Every row has its stable ID, and related records carry ID and name.
- Dates, time zones, currency, decimals and units follow the written rules.
- Empty, zero and unknown values can be told apart.
- A row with commas, quotes and line breaks opens as expected.
- Accented and non Latin test text displays correctly in the intended programs.
- Identifiers with leading zeros and long digits survive using the documented method.
- Formula test cells are handled as designed and checked in the intended spreadsheet programs.
- Only permitted users can export, and columns respect permissions.
- Zero row, large export and failure cases behave as agreed.
- File name, retention and repeat download behaviour match the brief.
FAQs
Should the export match the screen or the stored data? Decide per export and write the decision down. A screen style export suits people reviewing results, with friendly labels and readable names. A data style export suits reconciliation, with stable IDs and agreed value formats. Some projects need both, and others need only one. If you fund only one, keep the ID columns so any row can be traced back, and tell users which style they are getting so that nobody expects the other.
Is wrapping every cell in quotes enough to make the file safe? No. Quoting handles commas, quotes and line breaks. Formula risk is a separate problem, and OWASP warns that some escaping can fail after a spreadsheet saves and reopens a file and that no single strategy suits every program. Ask your developer to choose handling for the spreadsheet programs you use, test it with hostile sample text, and explain how it changes the values. If another system needs untouched values, treat that as a separate, restricted decision with trusted recipients and written handling instructions.
Can we treat the CSV as our backup? Not by itself. A CSV can carry IDs and the links between records through agreed columns, which makes it useful for review, reporting and reconciliation. But a file of rows does not by itself preserve the full state of the app, attachments, settings or history, and it does not give you a tested way to recover. A spreadsheet may also alter values when the file is opened and saved. If recovery matters, specify a backup and restore process as its own requirement and rehearse it.
Taking it further
If you are planning a web app and want the export written into the scope, my work with me page describes the website, web app and mobile development I offer. Bring a sample of the file you would like to receive, and the contract above can be adapted to it.