Contacts CSV import
Bulk-import contacts into a workspace from a CSV file. The import runs in the background: the file is uploaded to object storage, a worker parses and ingests it in chunks, and the UI polls progress in real time.Overview
- From the Contacts page, click Import CSV.
- Pick one or more
.csvfiles from disk (select multiple files to queue them sequentially). - Each file is uploaded, queued for processing, and you’ll see
Uploading → Queued → Processing → Complete ✓with a per-file progress bar and running counters. - Any failed rows are summarised with a View errors button — review them, fix in your spreadsheet, and re-upload the corrected file.
Required columns
Optional columns
Column headers are matched case-insensitively. Columns you don’t provide are
ignored — extra columns are safely skipped.
Phone format
All phone numbers must normalise to E.164 — an international format that starts with+, the country code, and digits only (no spaces, dashes, or
parentheses).
Tip: if your spreadsheet strips the leading
+ or adds scientific notation,
format the phone column as Text before exporting.
Example CSV
Limits
- File size: 10 MB per file.
- Row count: recommended max 50,000 rows per file. Split larger lists across multiple files and upload them in one go — they’ll process sequentially.
- Encoding: UTF-8. Export from Excel as “CSV UTF-8 (Comma delimited)” on macOS, or “CSV (Comma delimited) (*.csv)” with UTF-8 encoding on Windows.
- Line endings:
\nor\r\nboth supported. - Quoting: standard CSV quoting — wrap a field in
"…"to include commas or newlines, escape a quote inside as"".
Troubleshooting
“missing name” / “missing phone” The row is missing a required value. Make sure the header row usesname and
phone (or one of the listed aliases) and every data row has both fields
populated.
“invalid phone format”
The phone number could not be normalised to E.164. Add a country code with a
leading + (e.g. +39…, +1…) or remove non-digit characters that break
parsing.
“duplicate phone number (contact already exists)”
Another contact in the workspace already has this phone number. Imports
don’t overwrite existing contacts — delete or update the existing record
first if you need to replace it.
Import stuck on “Queued”
The background worker is temporarily behind on jobs. The UI will catch up
automatically when the worker picks up the job — leave the page open, or
check back later; the job state is persisted server-side.
“upload failed: 413”
Your file exceeds the 10 MB cap. Split it into smaller files and upload them
together.
How it works
Under the hood:POST /api/contacts/importaccepts multipart form data, validates the file, and uploads it to thecontact-importsobject-storage bucket at{workspaceId}/{importId}/{filename}.- A row is inserted in
public.contact_import_jobswith statuspending, and acontacts.import.processoutbox job is queued. - The worker claims the job, downloads the file, parses rows, normalises
phones, and inserts contacts in chunks of 100 — updating
processed_rows,successful_rows,failed_rows, anderror_reportafter each chunk. GET /api/contacts/import/:idreturns the current state for the UI to poll.