Skip to Content

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

codestring

The 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": code
initialValuenumber

The original face value at issue. Set once — Shopify computes current balance as initialValue minus the sum of debits.

"initialValue": face_value
expiresOnstring (ISO8601 date)

Expiry date. Omit for cards that never expire.

"expiresOn": expires_at
notestring

Internal admin note.

"note": "Migrated from " & source_platform

Customer & recipient

Source-platform IDs here. Graftport rewrites them to Shopify GIDs at load time.

customer_idstring

Source-platform customer ID for the cardholder. Resolved to a Shopify customer GID using the customer migration’s results.

"customer_id": customer_id
recipient_idstring

Source-platform customer ID for the gift recipient (when the card was purchased as a gift for someone else). Also resolved to a Shopify GID.

messagestring

Personal gift message.

preferredNamestring

What 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:

FieldNotes
processedAtISO8601 of when the debit happened.
noteOptional 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.

  • initialValue is 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 $giftcode for 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_id are 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 expiresOn in 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 DebitTransaction schema; malformed entries (missing debitAmount, unparseable processedAt) fail the row.
Last updated on