Skip to content
datahacks.dev
CSV

Designing a CSV Import Screen Users Can Succeed With

What a CSV import screen needs so users succeed: a template, clear limits, column mapping, a preview, plain-language errors and a count before commit.

7 min read
Summarize in AI
Flat illustration of a spreadsheet grid with a selected cell and a formula bar, with the GNU Bash logo.

A CSV import screen has one job: let a person with a spreadsheet get their data into your application without help. The job fails in a predictable way. The person uploads a file, something in it is not what the system expected, and the screen answers with "Invalid file" or with a red banner at the top of the page. They do not know which row, which column or what would work, so they try again, then email support. Every one of those cases can be prevented by a design that shows the person what the system understood and what it will do before it does it. This article describes that design, stage by stage, with the details that make the difference. It is the screen-level companion to CSV import and export features in applications, and the dry-run and error-report mechanics are in previewing a CSV upload before committing and reporting row-level errors back to the user.

Start before the upload

The first screen is where most errors are prevented. It has four elements, and each removes a class of mistake.

A template. A downloadable file with the exact header row and one or two example rows. Users who start from the template do not misspell headers, reorder columns or guess formats. Generate it from the same definitions that drive validation, so the two can never disagree, and give it a name that includes a version when the columns change.

The rules, in words. Next to the upload field: which columns are required, what format dates and numbers use, which values are allowed for the fields with a fixed list, and the limits (file size, maximum rows). State them in the user's terms: "Plan: free, pro or team", "Seats: a whole number, 1 or more", "Up to 10,000 rows per file". A limit the user finds out about after a two-minute upload is a design bug (limiting file size and row count for CSV uploads covers the numbers).

What happens to existing records. A sentence that says whether the import adds new records only, or updates those it finds, and by what key: "Customers are matched by email. Existing customers are updated; new ones are created." Without it, users do not know whether uploading is safe to repeat.

Format choices. Accept CSV and, if your users live in spreadsheets, .xlsx, and say which encoding you read. If you only accept UTF-8, say so, and tell Excel users how to save as "CSV UTF-8".

Show what you understood

After upload, do not start work. Show what the system found, and let the person correct it. The inspection step reports the delimiter it detected, the encoding, the number of rows and the header, in a sentence: "We found 4 columns and 2,310 rows, separated by commas." If the detection is wrong, because a semicolon file was read as commas and every row has one column, the user sees it immediately and can choose the delimiter, which is better than a failure message after processing.

Then the mapping. A table of the file's columns on the left and the application's fields on the right, each with a dropdown, pre-filled by matching the headers (case, spacing, common aliases, as in choosing column names users recognize). The user confirms or corrects, and required fields without a match are flagged in red. A column in the file with no target is shown as "ignored", so the user knows it will not be imported, and a user who sees Notes marked ignored when they wanted it imported can fix it now and not after.

Remember what the mapping is for. A file with headers in French, a file with an extra leading column, a file with the columns in another order all work if the user can map them, and none works if the importer insists on exact names.

Preview with real data

Show the first 10 to 20 rows as they will be understood, with the type each column was read as: the date shown as a date in the user's format, the number with its thousands separator, the text with leading and trailing spaces trimmed. Misreadings are visible at a glance in this view: a zip code shown as 2134 where the file has 02134, a date showing the wrong month, a column of names shifted into a column of cities by an unquoted comma. Showing parsed values is a far better test for the user than any message.

Then a summary of what Commit will do. In the test, a six-row file produced:

Text
dry run: {"created":1,"updated":2,"rejected":3}

On screen that is "1 new customer, 2 customers will be updated, 3 rows have problems" with a link to see the problems. This is the most important element on the page. It turns a vague "import" into three numbers a person can sanity-check against what they expected: if the file was meant to be 200 new customers and the summary says 200 updates, something is wrong, and the user finds out before anything has changed.

Errors in the user's words

The rows with problems should be listed in a form the user can act on. In the test, the report read:

Text
line,column,value,problem
4,plan,enterprise,"plan must be one of: free, pro, team"
4,seats,,"seats must be a whole number of 1 or more"
5,email,not-an-email,"not a valid email address"
6,plan,gold,"plan must be one of: free, pro, team"

Each line has the physical row in the user's file (with the header as line 1, so it matches the row number in their spreadsheet), the column using the user's own header name and not an internal field, the value that failed, and the rule. Two problems on one row, as on line 4, are both shown, so the user is not sent round the loop twice. Messages say what would work: "must be one of: free, pro, team" is a fix, and "invalid value" is a complaint. Avoid error text that blames ("you entered an invalid email"); the file is a dataset, and the message is a description of it.

For many errors, show a summary first: "128 rows have an unknown plan" with five examples, and the full list as a download. A table of 128 individual lines hides the pattern that one cell of a column was misspelled throughout. The downloadable file should contain the original rows plus an error column, so that the user can fix and re-upload only those rows.

Let the valid rows through, when you can

Some imports must be all-or-nothing, for instance a ledger that has to balance. Most do not, and refusing 2,300 good rows because 7 are bad is a design that makes people avoid the feature. Offer a choice, with the default that suits the data: "Import the 2,303 valid rows and download the 7 that need fixing" or "Cancel and fix the file first". Make the second the default only if partial import would leave the data inconsistent. If you import partially, make the next step obvious: the download of rejected rows is in the confirmation, not three clicks away.

After the commit

End with a report, not a toast. The numbers (created, updated, rejected), a link to the rejected rows, a link to the records that changed if the application can filter by import, and an undo if you built one. Keep a list of past imports, with who ran them and when, so the person who comes back tomorrow and wonders what they did can see it. The record also serves support: when someone writes "my import broke", the import ID shows the file, the mapping and the errors.

Small touches that matter

A few details make the difference between usable and merely working. Keep the user's file name on every screen, so they remember which one they are importing. Do not time out silently: for a large file, show progress and let them leave the page. Make the whole flow work with a keyboard and a screen reader, which means real tables for the mapping and the preview, labels on every dropdown and error text that is not conveyed by color alone. Write for non-engineers: "column" and "row" are fine, "delimiter" needs a word of explanation, and "constraint violation" is not for users. And test the screen with a real person and a bad file, because the errors you did not think of are the ones that matter.

When a single upload form is enough

If the import is rare, the file is produced by your own systems and the users are administrators who read the log, a plain form with a file field, a summary and a downloadable error report is enough, and the mapping stage is overhead. If the file always has the same layout because you control the template, skip mapping and validate the header strictly with a message that lists the expected and the found columns. Add stages as the audience widens: more users, more sources, more mistakes. The Mozilla Developer Network documents the browser side of file inputs in its file input guide, and the upload risks to design against are in OWASP's file upload cheat sheet.

Also inScripting
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.