Files
plunk/apps/wiki/content/docs/guides/importing-contacts.mdx
T
Andy GrunwaldandClaude Opus 4.7 844be42151 Document boolean and numeric CSV value typing
The preceding commits taught the import worker to coerce custom CSV
column values into JSON booleans and numbers via `coerceCustomValue`
in `apps/api/src/jobs/import-processor.ts`. Without a corresponding
docs update, users can't predict whether a cell like `01234` lands as
a string or as the number `1234`, or which segment-filter operators a
field will expose after import.

Add two bullets to the existing "Rules and limits" list in the
contact-import guide, adjacent to the **Date columns** bullet that
already documents value typing for ISO 8601 dates. The bullets mirror
that style and brevity: one names the boolean keyword set and the
toggle it unlocks in segment filters, the other names the numeric
pattern, the `gt`/`lt` operators it unlocks, and the deliberately
preserved-as-string forms (leading zeros, `+`-prefixed, scientific
notation) so users keep their IDs, zip codes, and phone numbers
intact.

No other content is touched. Closes the documentation gap for
useplunk/plunk#390.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-24 11:26:52 +02:00

117 lines
4.5 KiB
Plaintext

---
title: Importing contacts from CSV
description: Bulk-load contacts and their custom fields from a CSV file
icon: Upload
---
Plunk supports bulk-importing contacts from a CSV file — useful for migrating from another platform, loading an initial list, or syncing a large batch of contacts that you can't reasonably stream through `/v1/track`.
## CSV format
The CSV must have a header row. The only required column is `email`. Every other column becomes a custom field on the contact under `data.<columnName>`.
A minimal file:
```csv
email
[email protected]
[email protected]
[email protected]
```
A richer file with custom fields:
```csv
email,firstName,plan,signupDate
[email protected],Ada,pro,2026-01-15T00:00:00Z
[email protected],Grace,enterprise,2025-11-02T00:00:00Z
[email protected],Linus,free,2026-04-21T00:00:00Z
```
In this example, every imported contact ends up with `data.firstName`, `data.plan`, and `data.signupDate` set.
### Rules and limits
- **File size**: up to 5 MB per upload. For larger lists, split the file and run multiple imports.
- **Encoding**: UTF-8. Non-UTF-8 files may produce garbled custom field values.
- **Email column**: must be present and valid. Rows with missing or invalid emails are reported back as errors.
- **Reserved column names**: `id`, `subscribed`, `createdAt`, `updatedAt`, and the auto-generated URL variables (`unsubscribeUrl`, etc.) are silently filtered out. Don't include them as columns.
- **Date columns**: use ISO 8601 (`2026-05-06T12:00:00Z`) so they're typed as dates and become usable with `within` / `olderThan` segment operators.
- **Boolean columns**: `true`, `false`, `yes`, `no` (case-insensitive) are stored as booleans and get the boolean toggle in segment filters.
- **Numeric columns**: plain integers and decimals (`42`, `3.14`) are stored as numbers and become usable with `gt` / `lt` segment operators. Leading-zero values (`01234`), `+`-prefixed numbers, and scientific notation stay strings so IDs, zip codes, and phone numbers aren't corrupted.
- **Existing contacts**: if a row's email matches an existing contact, the import **updates** the contact (merging the CSV's columns into `data`). It doesn't create a duplicate or overwrite the whole record.
## Importing your CSV
import {Tab, Tabs} from 'fumadocs-ui/components/tabs';
<Tabs items={['Dashboard', 'API']}>
<Tab value="Dashboard">
1. Open **Contacts** and click **Import**.
2. Pick your CSV file. Plunk validates the header and shows a preview of the first few rows.
3. Confirm. The import is queued and runs in the background.
4. The Imports page shows progress and any per-row errors when complete.
</Tab>
<Tab value="API">
For automation, use the import API:
**Step 1 — upload the CSV**
```bash
curl -X POST {{API_URL}}/contacts/import \
-H "Authorization: Bearer sk_..." \
-F "[email protected]"
```
The response includes a `jobId`:
```json
{ "jobId": "imp_abc123", "status": "queued" }
```
**Step 2 — poll for status**
```bash
curl {{API_URL}}/contacts/import/imp_abc123 \
-H "Authorization: Bearer sk_..."
```
Poll every few seconds until `status` is `completed` or `failed`. The response includes counts:
```json
{
"jobId": "imp_abc123",
"status": "completed",
"totalRows": 12000,
"imported": 11985,
"updated": 8,
"skipped": 0,
"errors": [
{ "row": 47, "email": "bad@", "reason": "invalid_email" },
{ "row": 1042, "email": "@example", "reason": "invalid_email" }
]
}
```
`imported` counts new contacts created. `updated` counts existing contacts whose `data` was patched. `errors` lists per-row issues with row number and reason so you can fix and re-upload.
</Tab>
</Tabs>
## Tips
- **Unsubscribed contacts**: imported contacts are created subscribed by default. If your CSV represents users who never opted in, set up a workflow that filters out anyone you shouldn't email — or import them and then bulk-unsubscribe them with `POST /contacts/bulk-unsubscribe` to keep them on file but unmailed.
- **Custom fields appear in segments immediately**: as soon as the import finishes, you can build dynamic segments on any of the imported columns.
- **Idempotent reruns**: re-running the same CSV updates contacts in place rather than creating duplicates. If you fix a column and re-upload, all matching rows get the corrected value.
## Related
- [Custom fields](/guides/custom-fields) — how the `data.<column>` fields you import are typed and used.
- [Bulk operations](/api-reference/overview#contacts) — `bulk-subscribe`, `bulk-unsubscribe`, `bulk-delete` for mass actions on existing contacts (max 1,000 per call).