> For the complete documentation index, see [llms.txt](https://native-app-co.gitbook.io/stork-credit/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://native-app-co.gitbook.io/stork-credit/bulk-import/csv-import.md).

# CSV import

Credit hundreds of customers at once from a spreadsheet — for migrations from another app, or for campaigns.

A CSV import credits many customers in one go. It's the tool for migrating balances from another loyalty or store credit app, and for seeding credit for a marketing campaign.

## Starting an import

Open **Store credit import** in the app navigation and click **New import**, or click **Bulk credit import** in the **Tools** row on the home page. Drop your CSV onto the upload area, or click **Add file**, then click **Start import**.

The **Download the sample CSV template** link on the upload page gives you a correctly formatted starter file.

## File format

The first row must be a header row. Column order doesn't matter; names do.

```csv
email,amount,currency,expires_on,send_email
john@example.com,50.00,USD,2028-12-31,true
jane@example.com,25.50,USD,2028-06-30,false
```

| Column       | Required | Description                                                        |
| ------------ | -------- | ------------------------------------------------------------------ |
| `email`      | Yes      | The customer's email address. A customer is created if none exists |
| `amount`     | Yes      | The credit amount, e.g. `50.00`. Must be greater than zero         |
| `currency`   | Yes      | ISO currency code, e.g. `USD`, `EUR`                               |
| `expires_on` | Yes      | Expiry date. Leave the cell empty for credit that never expires    |
| `send_email` | No       | `true` to email the customer, `false` or empty to stay silent      |

`send_email` also accepts `y` and `yes` as true. Anything else is false.

### Date formats

`expires_on` accepts two formats:

* `YYYY-MM-DD` — for example `2028-12-31`
* `DD/MM/YYYY` — for example `31/12/2028`

{% hint style="danger" %}
**`MM/DD/YYYY` is rejected.** American-style slash dates are deliberately not accepted, because `03/04/2028` is genuinely ambiguous and guessing wrong sets an expiry on the wrong day. Use `YYYY-MM-DD` if you're unsure — it is never ambiguous.
{% endhint %}

{% hint style="warning" %}
**No commas inside values.** Fields are separated on commas, and quoted fields containing commas are not supported. A row whose column count doesn't match the header is skipped silently — it will not appear in the error count. Keep values comma-free.
{% endhint %}

## Import limits

Your plan sets how many files you can import per billing period, and the maximum number of data rows per file:

| Plan     | Imports per month | Rows per import |
| -------- | ----------------- | --------------- |
| **Free** | 3                 | 20              |
| **Grow** | 10                | 1,000           |
| **Pro**  | 100               | No limit        |

**Imports per month** — every uploaded file counts as one import, including imports that fail or are aborted. The count resets with your billing period (see [Plans & billing](/stork-credit/reference/plans-and-billing.md#monthly-limits)). The upload page shows how many imports you've used and the date the count resets. Once the allowance is gone, importing is switched off until then or until your plan allows more: **New import** on the imports list is disabled, the list says why and when the count resets, and the upload area and **Start import** button on the upload page are disabled too.

**Rows per import** — the row count is checked **before** the import starts, so an oversized file is rejected immediately with nothing credited. Split it into several files or upgrade — see [Plans & billing](/stork-credit/reference/plans-and-billing.md).

## One import at a time

Only one import can be queued or running per store. Starting another while one is active is blocked, with a link to the running job.

{% hint style="info" %}
This limit exists to prevent double-crediting. Re-uploading the same file because the first attempt looked stuck was the classic way to credit every customer twice.
{% endhint %}

## Watching progress

The imports list shows every job with its number, status, and progress.

| Status         | Meaning                                       |
| -------------- | --------------------------------------------- |
| **Queued**     | Accepted, waiting to start                    |
| **Processing** | Working through the rows                      |
| **Completed**  | Finished — check the success and error counts |
| **Failed**     | The file couldn't be processed at all         |
| **Aborted**    | You stopped it                                |

Progress updates as rows are processed. Open a job to see its **log**: one entry per row, naming the customer and the amount credited, or the reason the row failed.

{% hint style="info" %}
Per-row log entries are kept for **30 days**. After that the job itself stays in your import list with its success and error counts, but the row-by-row log is no longer available.
{% endhint %}

## Aborting

Click **Abort** on a running import to stop it. Rows already processed **keep their credit** — aborting stops further processing, it does not roll anything back.

## Row errors

A row failing doesn't stop the import; the rest continue. Each failure is logged with a reason:

| Error                       | Cause                                                   |
| --------------------------- | ------------------------------------------------------- |
| *Missing required fields*   | `email`, `amount`, or `currency` was empty              |
| *Invalid email format*      | The address isn't a valid email                         |
| *Invalid amount*            | Not a number, or zero or negative                       |
| *Unsupported date format*   | `expires_on` wasn't `YYYY-MM-DD` or `DD/MM/YYYY`        |
| *Failed to create customer* | Shopify rejected the new customer — usually a duplicate |

Fix the failed rows in a new CSV containing **only those rows**, and import again. Re-importing the whole file would credit the successful rows a second time.

{% hint style="danger" %}
Imports have no de-duplication. Uploading the same file twice credits every customer in it twice.
{% endhint %}

## Interrupted imports

If processing is interrupted — a deploy, a restart — the job resumes automatically and skips the rows it already completed, so nobody is credited twice. A resumed job logs how many rows it skipped.

Jobs left stuck for more than 24 hours are not auto-resumed, deliberately: by then the file may have been re-uploaded as a new job, and resuming the original could double-credit. [Contact support](/stork-credit/reference/support.md) if you have a job in that state.

## Before a large import

* Test with a handful of rows first and confirm the balances in the Shopify admin.
* Decide about `send_email` up front — you cannot un-send notifications.
* Check the currency matches what those customers actually check out in.
* Keep the source file. It is your record of what was issued.
