> For the complete documentation index, see [llms.txt](https://docs.giftcardfactory.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.giftcardfactory.app/bulk-operations/store-credit-import-from-file.md).

# Store Credit Import From File

The **Store Credit Import from File** feature adjusts Shopify store credit balances for multiple customers — and [B2B company locations](#b2b-company-rows) — from a CSV file. Each row becomes one transaction — positive amounts **add** credit, negative amounts **subtract** credit — and you can configure amount, currency, and expiry independently per row. Positive and negative rows, and customer and company rows, can be mixed in the same file. Optionally, the app can send a notification email per transaction (customer rows only).

> **When to use this vs other store credit operations:**
>
> * Use this when amounts, currencies, or expiry dates differ per customer.
> * Use [**Store Credit Bulk Add**](/bulk-operations/store-credit-bulk-add.md) when every recipient should receive the same amount and configuration.

{% hint style="warning" %}
**Prerequisite: new customer accounts.** Per Shopify, store credit can only be redeemed at checkout when the customer is authenticated via [**new customer accounts**](https://shopify.dev/docs/api/admin-graphql/latest/objects/StoreCreditAccount). If your store uses classic customer accounts, recipients won't be able to spend the credit. Check or switch under **Shopify Admin → Settings → Customer accounts**.
{% endhint %}

### 💡 **Common Use Cases**

* **Bulk refunds** — issue store credit to many customers from a refund report.
* **Loyalty rewards** — different credit amounts per customer tier.
* **Compensation campaigns** — credits sized per affected order.
* **Account migrations** — bring in credit balances from another platform.
* **Migration corrections** — subtract amounts customers redeemed on your old system after the initial import.
* **Bulk debits / clawbacks** — remove credit from many customers at once (e.g. after a promotional error or refund reversal).
* **Multi-currency stores** — different currencies per customer market.
* **Referral / affiliate payouts** — credit referrers based on a tracked list.

### **🪜 Step-by-Step Instructions**

<figure><img src="/files/Y1RUQOfe7abjCEOQcp8b" alt=""><figcaption></figcaption></figure>

1. **Download a template.**

{% file src="/files/JIDqhHSlSejV82cAjBYd" %}

{% file src="/files/vA2q3lSlNKIj2JglXitZ" %}

2. **Fill in your data.** See [CSV Columns](#csv-columns) below.
3. **Upload the file.** The form accepts `.csv`, `.tsv`, and `.txt` files up to **10 MB**. After upload, a summary shows the detected separator, row count, and column names. Validation errors (if any) are listed.
4. **Resolve ambiguous dates.** If `expires_at` contains dates like `01/05/2026`, the form asks whether the first number is the day or month and applies it to all rows.
5. **Configure customer notification (optional).** When the file contains an `email` or `customer_id` column, a **Send store credit notification** checkbox appears. Enable it and choose a sending method.
6. **Choose internal delivery options.** Allow data download and / or send yourself a summary email.
7. **Click Import now or Schedule.**

### **📋 CSV Columns**

#### Required

* **`amount`** — The transaction value. Use a **positive number** (e.g. `25.50`) to add store credit, or a **negative number** (e.g. `-25.50`) to subtract from the customer's existing balance. Use a dot as the decimal separator. Commas are auto-converted to dots, but more than two decimal places are rejected. See [Subtracting Store Credit (Debits)](#subtracting-store-credit-debits) for details on how negative rows are processed.

#### Identifying the recipient (one of these is needed)

**Customer rows:**

* **`email`** — Customer email. If a customer with this email exists, store credit is added to them. **If no matching customer exists, a new customer record is created with that email and the credit is issued to them.**
* **`customer_id`** — Numeric Shopify customer ID. Use this to avoid creating a new customer when an existing one matches the email.

**Company rows** (B2B on Shopify — see [B2B Company Rows](#b2b-company-rows)):

* **`company_location_id`** — Numeric Shopify company location ID. Credits exactly that location.
* **`company_id`** — Numeric Shopify company ID. Credits **all** locations of that company.
* **`company_external_id`** — The company's external ID. Credits **all** locations of the matching company.

> **Precedence:** a row with a non-blank `email` or `customer_id` is always treated as a **customer row** — company columns on that row are ignored. Company columns apply only when both customer columns are blank. Among company columns: `company_location_id` wins over `company_id`, which wins over `company_external_id`.

> Rows with no recipient column filled in are skipped (logged as errors in the operation report).

#### Optional

* **`currency_code`** — 3-letter ISO 4217 currency code (e.g. `USD`, `EUR`, `GBP`). If omitted, the store's default currency is used. The currency must be enabled in your Shopify store.
* **`expires_at`** — Expiry date for the credit. Supported formats: `YYYY-MM-DD` (recommended), `DD/MM/YYYY`, `MM/DD/YYYY`, `DD-MM-YYYY`, `MM-DD-YYYY`. If omitted, the credit never expires.

### **👤 Customer Lookup & Creation**

The import uses the following resolution order per row:

1. If `customer_id` is present, look up that customer.
2. Otherwise, search by `email`.
3. If no customer matches by email, create a new customer with that email and issue the credit to them.

> Creating a customer here is the same Shopify operation as adding a customer manually — Shopify's standard "welcome" behavior applies based on your store's settings.

### 🏢 **B2B Company Rows**

Rows identified by `company_location_id`, `company_id`, or `company_external_id` add store credit to **B2B company locations** — the level where Shopify B2B checkout looks for it — instead of individual customers. Customer and company rows can be freely mixed in one file.

{% hint style="info" %}
Requires **B2B on Shopify** (Shopify Plus). When the app detects company columns in your file, it asks for permission to read your companies — grant it before running the import, otherwise company rows will fail.
{% endhint %}

**How company rows are processed:**

1. The location(s) are resolved: `company_location_id` → that exact location; `company_id` or `company_external_id` → **every** location of the company. Companies repeated across rows are only looked up once.
2. Positive amounts credit each resolved location; negative amounts debit each location's balance (same [cap / zero-out behavior](#subtracting-store-credit-debits) as customer debits).
3. The result CSV gets **one row per location** — so a `company_id` row for a 3-location company produces 3 result rows. Success/error counts still track CSV rows, not locations.
4. Unresolvable identifiers (no match, or an ambiguous `company_external_id`) log the row as an error — the app never guesses.

**Things to know:**

* **No notification emails** — company rows never send notifications, regardless of the notification checkbox.
* **Currency matters** — use the currency the company location orders in; credit in another currency won't be spendable at their B2B checkout.
* `expires_at` works for company credits exactly as for customers; buyers need **new customer accounts** to spend the credit.

### **➖ Subtracting Store Credit (Debits)**

Any row with a **negative** `amount` subtracts from the customer's existing store credit balance instead of adding to it. The same file can mix positive (credit) and negative (debit) rows freely — each row is processed independently.

**How debit rows are processed:**

1. The customer is looked up via `email` or `customer_id`. If no customer exists, the row is logged as an error (debits cannot create new customers — there'd be nothing to debit).
2. The customer's store credit account in the requested `currency_code` is fetched.
3. **If the customer has no store credit balance in that currency**, the row is logged as an error and skipped.
4. **If the requested debit is greater than the available balance** (e.g. row says `-50` but the customer only has `30` left), the account is **zeroed out** and an info-level message is logged. The row is counted as a success — over-debit never fails the row.
5. The amount actually debited and the new balance are written to the result CSV. Debits show as negative numbers in the **Balance Change** column.

> Debit rows ignore the `expires_at` column — debits don't have an expiry date.

### **✉️ Customer Notification (Optional)**

When the file contains an `email` or `customer_id` column, you can choose to email each customer about their transaction. Enable **Send store credit notification** and pick a sending method. Notifications are sent for **both increases and decreases** when opted in. Company rows are never notified — the setting only applies to customer rows.

* **Shopify Email** — uses Shopify's built-in **Store credit issued** notification email. (Note: Shopify's built-in template only covers credits; for debit notifications use App Email, Flow, or Klaviyo.)
* **App Email** — uses Gift Card Factory's email infrastructure with a customizable template (built from the **Store credit import from file** or **Custom** template type). Preview before sending.
* **Shopify Flow** — fires a Flow trigger; you configure the action that sends the email.
* **Klaviyo** — fires a Klaviyo metric; you configure a flow that responds to the metric.

To differentiate copy for credits vs debits in your App Email / Klaviyo template, branch on `{{ issued_store_credit.transaction_type }}` — its value is either `"credit"` or `"debit"`. Example:

```liquid
{% if issued_store_credit.transaction_type == "debit" %}
  We've adjusted your store credit balance down by {{ issued_store_credit.amount | format_money: shop.currency_formats.money_format }}.
{% else %}
  We've added {{ issued_store_credit.amount | format_money: shop.currency_formats.money_format }} of store credit to your account.
{% endif %}
Your new balance is {{ issued_store_credit.balance_after_transaction | format_money: shop.currency_formats.money_format }}.
```

[Read more about each sending option](/misc/sending-customer-notifications.md).

### **📤 Internal Delivery Options**

Independent of customer notifications:

* **Allow store credits data download**: Creates a download link in the **Bulk Operations** section. Expires after **3 days**.
* **Send internal email with all generated store credits**: Sends a list of credit transactions to an email address you specify (multiple addresses allowed, comma-separated).

### **▶️ Running the Operation**

* **Import now**: Runs the operation immediately.
* **Schedule**: Runs the entire operation at a future date and time you select.

> Per-row scheduling via a `scheduled_on` column is **not** supported for store credit imports — only for [gift card imports](/bulk-operations/import-from-file/multiple-imports-from-a-single-file.md).

### **🧪 Examples**

#### Simple credits

```csv
amount,email
25.00,customer1@example.com
50.00,customer2@example.com
10.00,customer3@example.com
```

#### Credits with expiry

```csv
amount,email,expires_at
25.00,customer1@example.com,2027-12-31
50.00,customer2@example.com,2027-06-30
```

#### Multi-currency credits

```csv
amount,email,currency_code,expires_at
25.00,us-customer@example.com,USD,2027-12-31
20.00,uk-customer@example.com,GBP,2027-12-31
30.00,eu-customer@example.com,EUR,2027-12-31
```

#### Using customer IDs

```csv
amount,customer_id,currency_code
25.00,123456789,USD
50.00,987654321,USD
```

#### Mixed credits and debits (e.g. migration corrections)

```csv
amount,email,currency_code
-25.00,jane@example.com,USD
-10.50,john@example.com,USD
50.00,sarah@example.com,USD
```

#### B2B company rows (mixed with customers)

```csv
amount,email,company_id,company_location_id,company_external_id,currency_code
100.00,,123456789,,,USD
250.00,,,987654321,,USD
75.00,,,,ACME-01,USD
25.00,vip@example.com,,,,USD
```

> Row 1 credits **all** locations of company `123456789`; row 2 credits one specific location; row 3 matches a company by external ID; row 4 is a regular customer row.

### **📄 Result File Output**

After the operation finishes, a result CSV is generated with one row per transaction (company rows produce one row **per credited location**):

| Column               | Description                                                                |
| -------------------- | -------------------------------------------------------------------------- |
| **Id**               | Store credit account ID.                                                   |
| **Recipient**        | Customer first + last name, or the company name for company rows.          |
| **Email / Location** | Customer email, or the company location name for company rows.             |
| **Currency**         | Currency code of the transaction.                                          |
| **Balance Change**   | Amount applied. **Positive** for credits, **negative** for debits.         |
| **Current Balance**  | The account's store credit balance in that currency after the transaction. |

### **🚫 Limitations**

* **File size**: 10 MB maximum.
* **Currency must be enabled** in your Shopify store settings to be valid for that customer.
* **Debits require an existing store credit account** in the requested currency — debit rows for customers with no balance are skipped.
* **Debits ignore `expires_at`** — only credit transactions can have an expiry date.
* **No per-row scheduling** — use the Schedule button to run the whole import later, but every row runs together.

### **FAQ**

**My CSV has decimals like `25,50` and they're being rejected.**\
The decimal separator must be a dot (`.`). Commas are auto-converted, but check for currency symbols (`$`, `€`) or thousands separators in the same field — those are rejected.

**A row was skipped — why?**\
Rows are skipped if `amount` is missing or invalid, or if neither `email` nor `customer_id` resolves to (or creates) a customer. Check the operation log under Bulk Operations → details.

**A customer didn't get a notification email.**\
Confirm **Send store credit notification** was enabled, the row had an `email` or `customer_id` column with a valid value, and your sending method is configured (Shopify Email's Store credit issued template enabled / App Email domain validated / Flow configured / Klaviyo flow listening on the metric).

**I imported the same file twice — did customers get double credit?**\
Yes — the import always processes each row as a new transaction, it doesn't deduplicate. If you re-ran an import, each customer in the file will have been credited (or debited) twice. To reverse a duplicate run, re-upload the same file with the `amount` values negated.

**Can I use this to remove (debit) store credit?**\
Yes. Use a negative number in the `amount` column (e.g. `-25.00`) to subtract from a customer's existing balance. See [Subtracting Store Credit (Debits)](#subtracting-store-credit-debits).

**What happens if I try to debit more than the customer has?**\
The customer's account is zeroed out — only the available balance is debited — and the over-debit is recorded in the operation log. The row is counted as a success, not an error.

**Can I debit a customer who has never had store credit?**\
No. Debits require an existing store credit account in the requested currency. Rows trying to debit a customer with no balance in that currency are logged as errors and skipped. The same applies to company locations.

**My row has both an email and a company ID — who gets the credit?**\
The customer. Customer columns (`email` / `customer_id`) always take precedence; company columns on that row are ignored. Leave both customer columns blank to make it a company row.

**Why did one CSV row produce several result rows?**\
`company_id` and `company_external_id` credit **every** location of the company — the result file shows one row per location. Use `company_location_id` to target a single location.

**My company rows all failed with a permission error.**\
The app needs permission to read your companies. Re-upload the file and grant the permission when prompted (the app asks as soon as it detects company columns), then run the import again.
