---
title: "Importing a CSV"
description: "Start a table from a CSV or add rows to an existing one. Covers how column types are inferred, why inference is conservative, how columns are matched on re-import, and the file limits."
section: "Tables"
canonical: "https://banksync.io/docs/tables/tables-csv-import"
---

You can build a table from a CSV, or add rows to a table you already have. Either way BankSync shows you the schema it proposes and waits for you to accept it — inference is a suggestion, never a silent decision about how your data is typed.

## The short version

- Import into a **new table** when the file defines the shape; review every inferred field before accepting it.
- Import into an **existing table** when its fields already define the shape; headers match field labels exactly.
- Set identity fields before repeated imports if matching rows should update. Without identity, every imported row appends.
- Invalid rows are reported individually, while valid rows continue. Blank cells never erase an existing value.

## Importing into a new table

**Start a table from a CSV**

1. **Choose the file** — The first row is read as the header row. A UTF-8 byte-order mark, which Excel writes, is
   stripped rather than becoming part of the first column's name.
2. **Review the proposed columns** — BankSync samples the first 200 rows and proposes a field and a type for each column, with a
   format already set.
3. **Correct anything** — Change a type, rename a column, or leave a column out.
4. **Import** — The rows are written through the same validation every other write path uses.

A table created from a CSV is a custom table, and it starts **append-only**: BankSync will not guess which of your columns identifies a row, because guessing wrong lets a later re-import overwrite real rows. Mark the key fields yourself when you want re-imports to update instead of add.

## How types are inferred

Inference is deliberately cautious. A column only becomes a number when **every** non-blank value in the sample is one, and the same for dates. Widening a column later — number back to text — is a lossless settings change, whereas discovering mid-import that `00123` was a postcode and not a quantity is not.

The order it tries matters:

1. **Checkbox**, so a column of `0` and `1` is a flag rather than a measure.
2. **Number.**
3. **Date**, so a bare `2026-01-31` stays a date rather than becoming a timestamp.
4. **Date & time.**
5. **Text** otherwise.

A text column with a small number of repeated values — 25 distinct or fewer — is additionally offered as a **Select**, with those values as its options.

Columns with a blank header are skipped and named in the summary, and only the first 100 columns become fields; anything past that is listed as ignored rather than quietly dropped.

## Importing into an existing table

Columns are matched by **exact header-to-label match**, never by position. If your header says "Transaction Date" and the column is called "Date", the import tells you it could not place that column and refuses, rather than guessing — a positional guess writes a vendor name into whatever column shares the index, which a text column accepts without complaint and the import then reports as a success.

Fields that cannot receive an imported value are not treated as unmatched, because they are not targets at all: computed fields, relations, attachments, auto numbers and system fields.

If the table has key fields set, re-importing the same file updates the matching rows instead of adding duplicates. If it does not, every row in the file is added.

A row the file cannot supply cleanly — a date that does not parse, a letter in a number column — is **reported, not fatal**. A 5,000-row export with three bad dates imports 4,997 rows and tells you about the three. This is deliberately different from the REST API, which rejects a bad batch whole: an import is a bulk migration, not a transactional write.

> **Blank cells are left alone:** 'A blank cell in the CSV is skipped rather than written as an empty value, so re-importing a partial export does not wipe columns the file does not carry.'

## Limits

| Limit                           | Value |
| ------------------------------- | ----- |
| CSV file size                   | 25 MB |
| Columns imported                | 100   |
| Rows sampled for type inference | 200   |

Row quotas apply as normal: an import that would take you past your plan's stored-row limit fails and says so rather than importing part of the file.

## Where to go next

- [Field types in a table](/docs/tables/tables-fields) — what you can change a proposed column into.
- [Creating and editing a table](/docs/tables/tables-create) — the rest of the schema editing story.
