Customers CSV format

The customers CSV powers the bulk import at /dashboard/customers/import and the export at /dashboard/customers/export. Structurally it's the same flow as the products CSV — upload, preview, apply, with a column mapping UI for non-matching headers — but the schema is narrower.

Only one column is required: email. Everything else is optional.

Canonical columns

email,name,status,notes,creditLimit
ColumnRequiredDescription
emailCustomer's email. Matching key on import. Lowercased before comparison.
nameCustomer or company display name.
statusACTIVE or BLOCKED. Defaults to ACTIVE if omitted.
notesFree-text internal notes. Not shown to the customer.
creditLimitOptional credit limit in your currency. Can be blank.

Header name matching

Same rules as products: headers are lowercased and stripped of spaces, underscores, and dashes, then matched against a synonym list.

CanonicalAlso recognized
emailemailaddress, e-mail, contactemail, contact_email
namefullname, full_name, customername, customer_name, contactname, contact_name, companyname, company_name
statusstate
notesnote, comments, comment, remarks
creditLimitcredit_limit, credit, limit, creditline, credit_line

A CRM export with a Company Name column maps to name automatically. Credit Line maps to creditLimit. Anything the parser can't match, you can pick manually from a dropdown in the import preview.

Validation rules

email

  • Type: valid email address
  • Trimmed of surrounding whitespace
  • Max length: 254 characters
  • Lowercased before storage and matching
  • Required — empty or invalid emails fail the row

Validated against a standard email format, so things like bob@example without a TLD or not an email get rejected.

Must also be unique within the file. Duplicates are flagged: Duplicate email "bob@acme.com" also on row 47

name

  • Type: string (optional)
  • Trimmed
  • Max length: 100
  • Empty string or missing → stored as null

status

  • Type: enum of ACTIVE / BLOCKED (optional)
  • Default: ACTIVE if omitted or empty

The parser is generous about what counts as "blocked":

Value (case-insensitive)Parsed as
blocked, suspended, inactive, disabled, false, 0BLOCKED
anything else (including empty)ACTIVE

So a CSV exported from another system that uses Inactive as the status column will import correctly.

notes

  • Type: string (optional)
  • Trimmed
  • Max length: 2000
  • Empty or missing → stored as null

These are internal notes — only staff see them. They're never displayed to the customer.

creditLimit

  • Type: number (optional)
  • Must be non-negative if present
  • Blank or missing → stored as null
  • Rounded to 2 decimal places (cents)

Distribu doesn't enforce the credit limit automatically today — it's a reference field for your staff to see at-a-glance how much headroom a customer has. Setting it here just populates that display.

How imports match existing customers

Rows match on email, lowercased, scoped to your company. For each row:

  • No existing customer with that emailcreate.
  • Existing customer, every field identicalunchanged (no-op on apply).
  • Existing customer, any field differsupdate.

"Unchanged" compares name, status, notes, and creditLimit against the stored values.

The preview shows a sample of up to 50 rows with the email, name, and action so you can sanity-check which ones will get touched.

Plan limit enforcement

Customer imports respect your plan's customer cap:

  • Starter — 25 customers
  • Growth — 250 customers
  • Enterprise — unlimited

If the projected total (current + new creates) exceeds the limit, the apply is blocked with:

Import would exceed your plan's customer limit (25). Upgrade or reduce new rows.

Updates don't count against the limit — only new rows do.

See Billing → Plans for the full table.

Transactional apply

Same as products: the commit runs inside a single database transaction. All creates and updates land together or not at all. The bulk import is logged in the audit trail as CustomerBulkImported with metadata showing the number of rows created and updated.

What's NOT in this CSV

A few things customers have that aren't importable via CSV today:

  • Customer contacts (the multi-user storefront logins). Add these manually at the customer's detail page. See Customer contacts.
  • Addresses. Shipping addresses are per-customer and managed separately in the customer detail page, or by the customer on the storefront.
  • Price overrides. Per-customer pricing is managed in its own UI at the customer's detail page. See Price overrides.
  • Tax rate overrides. Per-customer tax overrides (taxRateOverride) exist on the customer record and can be set via the dashboard or the Customers API, but there's no CSV column for them today.
  • Storefront login credentials. Distribu sends storefront customers a passwordless login; there are no passwords in the CSV.

If you need to bulk-load contacts, addresses, price overrides, or tax overrides, email support@distribu.app — we can help with a one-off migration until those CSVs ship.

Sample template

Download the live template at /dashboard/customers/import/template:

email,name,status,notes,creditLimit
acme@example.com,Acme Restaurant,ACTIVE,Net-30 terms,5000.00
blueplate@example.com,Blue Plate Diner,ACTIVE,,

Two rows showing a fully-populated customer and a minimal one with just email + name.

Export format

The export at /dashboard/customers/export uses the same column order and headers as the import, so round-trip editing works the same way.

  • Filename: customers-{YYYY-MM-DD}.csv
  • Sort order: name ascending, then email ascending
  • null values serialized as empty string
  • creditLimit formatted to 2 decimals (5000.00, not 5000)
  • status serialized as the uppercase enum literal (ACTIVE / BLOCKED)

Empty customer lists still export a header-only CSV.

Common pitfalls

Outlook and Mac Mail export formats

Outlook's "Export contacts" produces a CSV with many columns — often E-mail Address, Full Name, etc. All of those map via the synonym list; just upload the raw export and adjust the mapping dropdowns if anything is off.

Emails with uppercase or mixed case

Every email is lowercased before matching, so Bob@Acme.com and bob@acme.com are the same customer. You don't need to pre-lowercase your CSV.

Re-enabling a blocked customer

Set status back to ACTIVE (or any value not in the blocked list) and re-import. The customer will flip from BLOCKED to ACTIVE on the next apply.

Clearing a credit limit

Leave the cell empty. 0 would be stored as a zero credit limit (no credit), which behaves differently from "no limit tracked."

One-off contacts vs companies

Distribu customers are accounts, not individual contacts — one row per buying entity. If you want multiple humans at one account to log in to the storefront, add them later as contacts (multi-user customers). See Multi-user customers.


Next: Orders export.