{"slug":"spreadsheet-import","meta":{"title":"Spreadsheet Import","slug":"spreadsheet-import","kind":"pattern","summary":"Bring an existing spreadsheet into the system by mapping its columns, previewing what will happen, and reporting per-row results — never all-or-nothing.","problem":"The data already exists in a spreadsheet, and the only way in is to retype it. So people either do not adopt the system, or they keep the spreadsheet as the real record and treat the software as a copy that drifts.","family":["capture"],"data_shape":["collection"],"principles":["friction","orientation","progressive-disclosure"],"interaction":["editing"],"density":"medium","complexity":"high","status":"stable","visibility":"public","use_when":["The data exists elsewhere, in volume, and retyping is the barrier to adoption.","Records are homogeneous, so a column mapping is meaningful.","Imports repeat — monthly price lists, weekly timesheets, a migration."],"avoid_when":["It is a one-off of ten rows. Quick add is faster than building this.","The records are heterogeneous, so no column mapping applies.","An API or a direct integration is available and would keep the data in sync rather than copying it once."],"alternatives":[{"slug":"quick-add","when":"A handful of records, typed where they belong."},{"slug":"minimal-form","when":"Records are created one at a time as real-world events happen."}],"ask_leo":"Build a spreadsheet import for this record type.\n\n- Accept the file formats people actually have, including .xlsx and .csv, and\n  accept a paste of tabular data too.\n- Guess the column mapping from the headers, then SHOW the guess and let it be\n  corrected. Never map silently.\n- Show a preview of the first rows exactly as they will be created, with the\n  values converted — dates parsed, numbers stripped of currency symbols — so\n  people see the interpretation, not the raw text.\n- Validate every row before importing anything, and report problems per row\n  with the row number and the specific issue. Never fail the whole file for one\n  bad row.\n- Let people choose what happens to invalid rows: skip them and import the\n  rest, or stop. Skipping is usually right, and the skipped rows must be\n  downloadable so they can be fixed and re-imported.\n- Say what will happen to rows that match existing records — create, update or\n  skip — and make that an explicit choice, not a hidden default.\n- Report the outcome as counts by category, and link to what was created.\n- Make the import undoable, or at least reviewable as a batch, for a period\n  after it runs.\n","related":[{"title":"Excel-to-app import QA checklist","url":"/cookbook/inline-editable-table","summary":"The table people land in after the import, where they will correct what came through."},{"title":"Minimal Form","url":"/patterns/minimal-form","summary":"The single-record path this pattern exists to avoid repeating 300 times."}]},"body":"## Anatomy\n\n```\n  1 UPLOAD        2 MAP COLUMNS            3 PREVIEW \u0026 VALIDATE     4 RESULT\n  ┌──────────┐    Your column → Field      Row 1 ✓ Riverside…       ✓ 284 created\n  │ drop a   │    \"Client\"   → Client      Row 2 ✓ Kestrel…         ✓  12 updated\n  │ file     │    \"Amt\"      → Amount      Row 3 ✗ Amount \"n/a\"     ✗   4 skipped\n  └──────────┘    \"Due\"      → Due date        is not a number        ↓ download\n                  \"Notes\"    → (ignore)                                the 4 rows\n```\n\nThe four steps are not decoration — each one prevents a specific failure:\n\n1. **Upload** accepts what people actually have, including a paste.\n2. **Mapping is shown and correctable.** A silent guess is how a phone number\n   column ends up in the reference field.\n3. **Preview shows the converted values**, so people check the interpretation\n   rather than the input.\n4. **Per-row results**, with the failures downloadable.\n\n## Why it works\n\nIt removes the single largest barrier to adopting an internal tool. Data that\nalready exists is not a nice-to-have — it *is* the business, and a system that\ncannot accept it is a system that will run alongside the spreadsheet forever.\n\nThe per-row failure model is what makes it usable in practice. An all-or-nothing\nimport of three hundred rows fails on row 214 and gives back nothing, so the\nperson fixes one cell and waits again. Importing 296 and handing back 4 to fix\nconverts an afternoon into five minutes.\n\n## The parts everyone skips\n\n- **The conversion preview.** People do not check raw text; they check meaning.\n  Showing `14/07/26` is useless — show that it will be stored as 14 July 2026,\n  and the day-month ambiguity gets caught before it corrupts three hundred rows.\n- **Match behaviour.** Whether an incoming row creates, updates or is skipped is\n  the highest-consequence decision in the whole flow, and it is usually a hidden\n  default.\n- **The downloadable failures.** Without them, \"4 rows failed\" means re-deriving\n  which four from a file of three hundred.\n- **Undo.** An import is a bulk action with a large blast radius. It is exactly\n  the case where [confirmation](/patterns/confirmation-vs-undo) is warranted, and\n  reviewability afterwards matters more than the dialog.\n\n## Getting it wrong\n\n- **All-or-nothing.** The defining failure of bad importers.\n- **Silent column mapping**, producing a plausible-looking import that is wrong\n  in one column.\n- **Errors reported without row numbers**, so the person cannot find them.\n- **No preview**, so date and number formats are discovered afterwards.\n- **Hidden update behaviour**, quietly overwriting records the person expected\n  to be created.\n- **Accepting only CSV**, when everyone has .xlsx and converting is an extra\n  step that loses formatting.\n\n## Exemplars\n\n**Stripe's product and price import** shows the preview-and-map flow done\ncarefully, because a mis-mapped price column has immediate financial\nconsequences.\n\n**Airtable's import** is the reference for match behaviour: choosing the key\nfield and what happens on a match is an explicit step, not a checkbox.\n\n**Mailchimp's contact import** is worth studying for the failure report — the\nskipped rows come back as a file you can fix and re-upload, which is the whole\nloop closed.\n\nThe extractable rule: **an import is a conversation about interpretation, not a\nfile transfer.** Every step exists to show the person what the system thinks\ntheir data means, before it commits.\n"}