Skip to content

CSV Import

Import CSV files into any view with a visual field mapping dialog. Map CSV columns to view fields, preview records before importing, and save mapping configurations for repeated use.

Opening the Import Dialog

There are two ways to start an import:

  • Toolbar — click the Import button in the top toolbar
  • File menu — click File > Import Data

The Import button is only enabled when the current view allows adding rows.

Upload

Select a .csv, .tsv, or .txt file. The dialog scans the file to detect columns and count rows without loading the entire file into memory — files up to 100 MB are supported.

Field Mapping

After selecting a file, the mapping table shows:

ColumnDescription
CSV FieldsValues from the current record, numbered by CSV column position
Table Target FieldsView column name with data type badge (e.g. VARCHAR(255)) and constraint badges (PRIMARY, UNIQUE)

Auto-Mapping

Columns are automatically matched by name:

  1. CSV header matches view column display name (case-insensitive)
  2. Fallback: matches field name (camelCase API name)
  3. Fallback: matches database column name (snake_case)

Unmapped columns show Default: <null> and are skipped during import.

Manual Mapping

Click any CSV cell to open a dropdown and reassign the CSV column for that row. Select to unmap.

Record Browser

Use the < > paginator at the bottom to browse through records and verify that CSV values land in the correct columns.

Options

OptionDescription
First line contains field namesWhen checked, the first CSV row is used as column headers for auto-mapping. When unchecked, columns are mapped by position.
Import MethodINSERT creates new rows. UPDATE updates existing rows (requires primary key mapped).
Align field names byControls CSV column ordering: File order (original position) or Alphabetical (sorted by header name).

Column Handling

Column TypeImportable?Notes
Regular columnsYesUser provides values
UUID primary keyYesMap from CSV or leave blank for auto-generation
Auto-increment primary keyNoDatabase generates automatically
FK columns (with edit mode)YesProvide the foreign key value
Relationship display columnsNoRead-only joined data
Computed / formula columnsNoCalculated at query time
Auto-set timestampsNoCreated/updated timestamps are generated automatically
Display groupsExpandedIndividual member columns shown separately

Saved Mappings

Save a mapping configuration by entering a name and clicking Save above the mapping table. Saved mappings can be loaded from the dropdown when importing the same CSV structure again.

Mappings are saved per view. Visibility can be Private (only you) or Shared (all view members).

Import Execution

Click Import to start. Rows are sent in batches of 500 to the server. A progress bar shows batch progress, success count, and error count.

Validation on import

Imported rows go through the same validation as a value typed into a cell: column constraints (required, max length, email, and the rest) and cross-field entity constraints. A row that fails is reported with its row number and reason, and skipped — the rest of the file still imports. Click Export Errors CSV to download every rejection with its details.

This means an import can partially succeed, which is deliberate: one bad row in a 5,000-row file shouldn't cost you the other 4,999.

TIP

Fix the rejected rows in the exported errors CSV and import that file again — it has the same columns, so your saved mapping still applies.

WARNING

Errors the database alone can detect — a unique value that collides with a row already in the table, a foreign key pointing at something that isn't there — are found when the batch is written, not during validation. Those fail the batch of 500 they belong to, and the report says so rather than blaming a single row. Validation catches the common cases first, so this is the exception.

API Endpoint

The bulk insert endpoint can also be called directly:

POST /api/data/bulk/{viewUuid}/insert

Request body:

json
{
  "rows": [
    { "name": "Angola", "code": "AO" },
    { "name": "Argentina", "code": "AR" }
  ]
}

Response (201):

json
{
  "viewUuid": "abc-123",
  "totalCount": 2,
  "successCount": 2,
  "failureCount": 0,
  "skippedCount": 0,
  "errors": []
}

Maximum 1000 rows per request. Field names use the camelCase API field names (same as the single-row insert endpoint).

Re-importing without duplicating

By default an import only ever adds rows, so running the same file twice gives you everything twice. Name the columns that identify a row and the same file becomes a correction instead:

json
{
  "rows": [{ "email": "[email protected]", "name": "Ada L." }],
  "conflictColumns": ["email"],
  "onConflict": "UPDATE"
}
onConflictWhat happens to a row that already exists
ERRORThe default. The database refuses the batch, and nothing is imported.
IGNOREThe stored row is kept and the incoming one is skipped. Skipped rows are counted in skippedCount, not failureCount — they are the requested outcome, not a failure.
UPDATEThe stored row's other columns are overwritten with the incoming values. The match columns themselves are never rewritten.

The match columns need a unique index

A database can only recognise a duplicate if a unique index or primary key says those columns identify a row. Without one, PostgreSQL refuses the statement and the import returns an error saying so — nothing is written. Add the index first, or import without a match column.

MySQL matches on any unique key

PostgreSQL matches on exactly the columns you name. MySQL has no equivalent, so it reconciles on any unique key on the table — a row colliding on a different unique column is updated too. Worth knowing if your table has more than one.

SchemaStack Documentation