# Importing contacts

> Bring contacts into Bookbag in bulk from CSV or JSON. Map core fields, route extra columns to custom attributes, and rely on external-ID upsert so re-running an import updates existing contacts instead of duplicating them.

Already have your customers in a spreadsheet or another system? **Import** brings them into Bookbag in bulk. You can import from CSV or JSON, and Bookbag matches on the external ID so re-running an import safely updates existing contacts rather than creating duplicates.

> **IMPORTS UPSERT BY EXTERNAL ID:** Every imported row needs an external ID. If a contact with that external ID already exists for the agent, the import updates it; if not, it creates a new one. This makes imports idempotent — run the same file twice and you get the same result, not duplicates.

## Before you import

1. **Decide your external ID** — Pick the stable, unique-per-agent identifier you'll use as the match key — your own customer ID, user ID, or email. Make sure every row has one.
2. **Define custom attributes you'll need** — Create any [custom attributes](/docs/contacts/custom-attributes) ahead of time so import columns map cleanly onto typed fields.
3. **Prepare your file** — Export a CSV or JSON with a column per field. Core fields map to name, email, and phone; everything else can map to custom attributes.

## CSV imports

A CSV import expects a header row naming each column, then one contact per row. Columns that match core fields populate those fields; any **extra columns** are routed into custom attributes by name.

```text
external_id,name,email,phonenumber,plan_tier,lifetime_value,vip
cus_001,Jordan Lee,jordan@example.com,+15551234567,pro,842,true
cus_002,Sam Rivera,sam@example.com,+15559876543,free,0,false
```

> **MATCH COLUMN NAMES TO ATTRIBUTE NAMES:** Extra columns map to custom attributes by their machine **name** (e.g. `plan_tier`, not "Plan tier"). Name your CSV headers to match the attribute names and the mapping is automatic.

## JSON imports

You can also import a JSON file — an array of contact objects with the same core fields and custom-attribute values. JSON is convenient when you're exporting straight from another system's API.

```json
[
  {
    "external_id": "cus_001",
    "name": "Jordan Lee",
    "email": "jordan@example.com",
    "phonenumber": "+15551234567",
    "plan_tier": "pro",
    "lifetime_value": 842,
    "vip": true
  }
]
```

## How fields are mapped

| Column / key | Maps to |
| --- | --- |
| external_id | The contact's external ID (required — the match key for upsert). |
| name | Core name field. |
| email | Core email field. |
| phonenumber | Core phone field. |
| Any other column | A custom attribute, matched by attribute name. Define the attribute first so the value lands in a typed field. |

> **DEFINE ATTRIBUTES FIRST FOR CLEAN TYPING:** If an extra column doesn't correspond to a defined custom attribute, its value can't be validated to a type. Create your [custom attributes](/docs/contacts/custom-attributes) before importing so numbers, booleans, and dates are stored correctly.

## Running the import

1. **Open the import dialog** — On the agent's Contacts tab, choose Import.
2. **Upload your CSV or JSON** — Select your prepared file.
3. **Review and confirm** — Bookbag parses the file and imports the contacts, upserting by external ID.
4. **Verify in the table** — Search and sort to confirm your contacts and attribute values landed as expected.

> **CHECK:** Because imports upsert, you can re-import a corrected file to fix mistakes — existing contacts are updated in place by their external ID.

## Importing at scale via the API

For large or recurring syncs, use the API instead of a file. The [Create contacts](/docs/api/v1/contacts-create) endpoint accepts a batch of contacts and upserts them by external ID — the same behavior as a dashboard import, driven from your own systems. This is the right approach for keeping Bookbag continuously in sync with your store or app.

## Common questions

**What happens if I import the same file twice?**

Nothing duplicates. Imports upsert by external ID, so the second run updates the same contacts in place instead of creating new ones.

**A column in my CSV didn't show up. Why?**

Extra columns map to custom attributes by name. If you hadn't defined an attribute with that name, the value may not be stored as a typed field. Define the attribute and re-import.

**Do contacts belong to the whole workspace or one agent?**

One agent. Import to the agent that should own those contacts; importing to a different agent creates separate records there.

**Is there a limit on how many contacts I can import at once?**

Dashboard imports handle bulk files comfortably. For very large or repeated syncs, the [Create contacts API](/docs/api/v1/contacts-create) accepts contacts in batches and is the recommended path.

## What's next

- [Custom attributes](/docs/contacts/custom-attributes) — Define the typed fields your import columns map onto.
- [Contacts overview](/docs/contacts/overview) — How contacts and external IDs work.
- [Create contacts API](/docs/api/v1/contacts-create) — Upsert contacts in bulk from your own systems.
- [List contacts API](/docs/api/v1/contacts-list) — Read contacts back out programmatically.
