Gift card mapping
StagingGiftcard is a flat shape — code, value, expiry, optional
recipient, and a debit history that’s passed through to Shopify
unchanged.
Gift cards depend on the customer migration. The staging model
declares requires_additional_mapping = customer so customer_id
/ recipient_id get rewritten from source IDs to Shopify GIDs at
load time. Run the customer migration first.
Quick reference — minimum loadable gift card
{
"original_id": gift_card_id,
"code": code,
"initialValue": face_value
}Identity & value
codestringThe redemption code on the card. Must be unique per shop and, to be
accepted by Shopify, 8–20 alphanumeric characters. If your source
codes are longer or contain other characters, wrap them in
$giftcode — codes that don’t
fit the format are rejected during the dry run, not silently dropped.
"code": codeinitialValuenumberThe original face value at issue. Set once — Shopify computes
current balance as initialValue minus the sum of debits.
"initialValue": face_valueexpiresOnstring (ISO8601 date)Expiry date. Omit for cards that never expire.
"expiresOn": expires_atnotestringInternal admin note.
"note": "Migrated from " & source_platformCustomer & recipient
Source-platform IDs here. Graftport rewrites them to Shopify GIDs at load time.
customer_idstringSource-platform customer ID for the cardholder. Resolved to a Shopify customer GID using the customer migration’s results.
"customer_id": customer_idrecipient_idstringSource-platform customer ID for the gift recipient (when the card was purchased as a gift for someone else). Also resolved to a Shopify GID.
messagestringPersonal gift message.
preferredNamestringWhat the recipient wants to be called in the card-delivery email.
Debit history (pass-through)
debitsobject[]Array of past debit transactions. Each entry has Shopify’s
DebitTransaction shape:
| Field | Notes |
|---|---|
processedAt | ISO8601 of when the debit happened. |
note | Optional reason. |
debitAmount | { amount, currencyCode }. |
"debits": redemptions.{
"processedAt": redeemed_at,
"note": "Order " & $string(order_id),
"debitAmount": {
"amount": $string(amount),
"currencyCode": currency
}
}Patterns
Source has just code + balance (no debit history)
If the source only tracks current balance, issue at the current balance and skip debits — the customer-visible amount is preserved even though the history is lost:
{
"original_id": gift_card_id,
"code": code,
"initialValue": current_balance,
"expiresOn": expires_at,
"note": "Migrated from " & source_platform & " — balance preserved, history dropped"
}Source has full debit history
{
"original_id": gift_card_id,
"code": code,
"initialValue": original_amount,
"expiresOn": expires_at,
"customer_id": customer_id,
"debits": redemptions.{
"processedAt": redeemed_at,
"note": "Order " & $string(order_id),
"debitAmount": {
"amount": $string(amount),
"currencyCode": currency
}
}
}Gift purchased for someone else
{
"original_id": gift_card_id,
"code": code,
"initialValue": face_value,
"customer_id": purchaser_customer_id,
"recipient_id": recipient_customer_id,
"preferredName": recipient_name,
"message": gift_message
}Long or non-compliant codes: $giftcode
Some platforms issue gift-card codes that Shopify won’t accept — typically too long (a 30-digit POS number) or carrying separators. You can’t truncate them: codes from the same batch often share a long common prefix, so cutting them down would make different cards collide.
$giftcode(raw) solves this. It converts any source code into a stable
16-character alphanumeric code — the same format Shopify itself
generates — using a deterministic one-way transform. The same input
always produces the same output, and different inputs effectively never
collide.
{
"original_id": gift_card_id,
"code": $giftcode(code),
"initialValue": current_balance
}If a row’s source code is empty — blank, missing, or nothing but dashes
and spaces — $giftcode returns nothing rather than inventing a code.
That row then fails validation with a missing-code error, which is what
you want: every blank row would otherwise be handed the same code, and
only the first of them could ever load.
Once you migrate a store with $giftcode, keep using it. The
transform is fixed — re-running a migration always yields the same
new code, so balances and re-runs stay consistent. Deciding to change
how codes are derived after cards are live would invalidate every
issued card.
Letting customers redeem their old card
Because $giftcode is deterministic, a customer holding an old printed
card can look up their new code themselves — no lookup list, no account
required. Graftport ships a small gift-card converter
you can embed on the storefront: the customer pastes their old code and
instantly sees the new one to enter at checkout. It runs entirely in the
browser and needs no app or backend — install is two lines pointing at a
hosted script. See the gift-card converter guide
for step-by-step Shopify instructions.
Spent cards need no action: a card migrated with a zero remaining balance simply has nothing to redeem.
Gotchas
Gift cards are real money. Always extract + dry-run transform first; spot-check a sample of staged records to confirm values and balances are right before loading.
initialValueis set once and can’t be changed. Decide upfront whether to issue at original face value (preserves history via debits) or current balance (drops history). Document the choice for the merchant.- Code format is enforced. Codes must be 8–20 alphanumeric
characters; anything else fails the dry run with a clear error. Use
$giftcodefor long or non-compliant source codes. - Code uniqueness is enforced. Duplicates fail the load (Shopify reports it per card, so you see exactly which ones). Suffix in the mapping if your source allows duplicate codes.
customer_id/recipient_idare source IDs, not Shopify GIDs. Graftport rewrites them — but only if those customers were migrated. If the customer wasn’t migrated, the link drops to null.- Currency. Each Shopify shop has a single gift card currency. Multi-currency cards from the source need to be converted in the mapping.
- Expired cards. Setting
expiresOnin the past creates an already-expired card on Shopify. Usually fine — preserves the historical state — but customers can’t redeem. - Debits are pass-through validated. Each entry runs through
Shopify’s
DebitTransactionschema; malformed entries (missingdebitAmount, unparseableprocessedAt) fail the row.