> For the complete documentation index, see [llms.txt](https://crushsuite.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://crushsuite.gitbook.io/docs/clubs/migrating-from-another-platform/preparing-your-member-csv.md).

# Preparing Your Member CSV

CrushSuite validates your CSV column by column before anything is imported. This reference lists every column, whether it's required, and the rules each value has to pass.

#### File format rules

* **Start from the template:** on the upload screen, click **Download CSV template** for a starter file with the right headers and an example row already filled in.
* **Encoding:** UTF-8. The byte-order mark some Excel exports add is stripped automatically. If you work in Excel, export as CSV first.
* **Size:** up to 4,000 rows and 2 MB per file. A larger list is fine; split it into several files and import them one after another. Members already imported are skipped, so overlap between files is safe.
* **Header row required**, plus at least one data row. A file with only headers is rejected with `CSV file is empty or has no data rows`.
* **Quote cells containing commas:** wrap the cell in double quotes and double any literal quotes inside it (`"she said ""hi"""`).
* **Leave optional cells empty**, not the word "null" or "none". A placeholder word is treated as a real value.
* **Corrections happen in the file.** The preview shows what's wrong with each row, but rows aren't edited on screen. Fix your CSV and click **Replace** to upload it again.
* The template includes `gateway_customer_token` and `gateway_payment_token` columns. Leave both blank unless you have completed a Stripe payment method transfer. See [Migrating Payment Methods from Vinoshipper](https://crushsuite.gitbook.io/docs/clubs/migrating-from-another-platform/migrating-payment-methods-from-vinoshipper).

***

#### Columns

Only `email` is required. Every other column has a sensible default, so leave anything you don't have blank rather than inventing a value.

<table><thead><tr><th width="201.8671875">Column</th><th width="172.11328125">Required</th><th>Accepted format</th></tr></thead><tbody><tr><td><code>email</code></td><td><strong>Required</strong></td><td>Valid email; must be unique in the file</td></tr><tr><td><code>firstName</code> / <code>lastName</code></td><td>Optional (recommended)</td><td>Text</td></tr><tr><td><code>phone</code></td><td>Optional</td><td>Any common format: <code>+14155552671</code>, <code>(415) 555-2671</code>, <code>415-555-2671</code>. Must be a real, assigned number, and unique in the file. A number without a <code>+</code> is read against <code>country_code</code> (US if blank)</td></tr><tr><td><code>member_number</code></td><td>Optional</td><td>Text, kept for reference. CrushSuite always assigns its own member number regardless</td></tr><tr><td><code>joined_at</code></td><td>Optional</td><td>Any parseable date; <code>YYYY-MM-DD</code> (e.g. <code>2024-06-15</code>) is safest. Anchors the member's first membership fee renewal. Blank defaults to one year before the import</td></tr><tr><td><code>date_of_birth</code></td><td>Optional</td><td><code>YYYY-MM-DD</code> only, e.g. <code>1985-03-15</code>. Passed to CrushSuite Compliance if installed</td></tr><tr><td><code>preferred_delivery_method</code></td><td>Optional</td><td>One of <code>SHIPPING</code>, <code>PICKUP</code>, <code>LOCAL_DELIVERY</code>. Becomes the default on every release order</td></tr><tr><td><code>address1</code> / <code>address2</code> / <code>city</code> / <code>province_code</code> / <code>country_code</code> / <code>postcode</code></td><td>Optional, but all-or-nothing</td><td>Text. See the address rule below</td></tr><tr><td><code>notes</code></td><td>Optional</td><td>Text, shown on the member's record</td></tr><tr><td><code>shopify_customer_id</code></td><td>Optional</td><td>Existing Shopify customer ID. CrushSuite uses it directly instead of matching on email</td></tr><tr><td><code>gateway_customer_token</code></td><td>Optional</td><td>The member's Stripe customer ID in <strong>your</strong> Stripe account (starts with <code>cus_</code>). Fill both token columns or neither</td></tr><tr><td><code>gateway_payment_token</code></td><td>Optional</td><td>The member's Stripe card ID in <strong>your</strong> Stripe account (starts with <code>pm_</code> or <code>card_</code>), taken from the <strong>new</strong> column of Stripe's mapping file. Tokens are assumed to be Stripe. Never a one-time <code>tok_</code> value</td></tr></tbody></table>

***

#### The address is complete or absent

If any of `address1`, `address2`, `city`, `province_code`, or `postcode` is filled in on a row, then `address1`, `city`, `province_code`, and `postcode` must all be present. A partial address fails with `Incomplete address — missing …`, naming the columns to add.

`country_code` doesn't count as "supplying an address". The template ships it as `US`, and filling it down the whole sheet is fine on rows with no address.

A member with no address at all imports fine. Pickup-only members legitimately have none. If a member with no address turns out to need shipping, their release order is held for your review instead of being billed with no destination.

***

#### If you're filling in the token columns

{% hint style="info" %}
A token on the wrong row bills the wrong member's card, and nothing at import can catch it, because a token is just an ID. Build the file from Stripe's mapping file rather than copying IDs by hand, and check that email, customer ID, and card ID line up on every row before you validate.
{% endhint %}

Use the **new** IDs from Stripe's mapping file, not the IDs in Vinoshipper's transfer file. Cards get a fresh ID when they land in your Stripe account, and the old IDs don't exist there.

A token that looks wrong doesn't block the member. The preview warns you when the two columns look swapped, when a value is a one-time `tok_` rather than a saved card, or when only one of the pair is filled. In each case the member still imports; they just don't get the transferred card and are asked to add one themselves instead.

***

#### Validation messages

After **Validate file**, every row shows as **Ready**, **Error**, or **Skipped**, with the reason in the **Issue** column. Rows with errors block the import until they're fixed: click **Download rows to fix** for a CSV of just those rows and their reasons, correct them in your file (or delete any you can't), and click **Replace** to upload again. Re-validating is free and creates nothing.

| Message                                                  | What it means                                                                                                                                                                                           |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"…" is not a valid phone number`                        | The number can't exist: unassigned area code, wrong length, or no such country. Check it, write it in full international format (`+14155552671`), or clear the phone cell for this member               |
| `Invalid date format (ISO date expected)`                | `joined_at` doesn't parse as a date                                                                                                                                                                     |
| `Invalid date — use YYYY-MM-DD format (e.g. 1985-03-15)` | `date_of_birth` isn't strict `YYYY-MM-DD`, or the date doesn't exist. A US-style `03/15/1985`fails                                                                                                      |
| `Incomplete address — missing …`                         | Some address columns are filled and others aren't. Complete the address or clear every address column on that row                                                                                       |
| `Duplicate email (also on row N)`                        | The same email appears on two rows                                                                                                                                                                      |
| `Duplicate phone (also on row N)`                        | The same phone appears on two rows, counted after formatting, so `(415) 555-2671` and `+14155552671` collide. Shopify rejects the second customer. Blank one of them out rather than inventing a number |
| `Already a member of this club`                          | Shown as **Skipped**, not an error. That email already has a membership here, so the row is left alone                                                                                                  |

A row with a missing or malformed `email` fails as well.
