Cross-resource lookups
Sometimes the value a mapping needs lives on another resource. A common example: a gift card export only carries the ID of the order it was bought with — the owning customer is two steps away:
gift card → its order → the order's customerA lookup follows those links for you. You build them on the editor’s Lookups tab; they are published, versioned, and previewed together with the mapping expression.
Building a lookup
A lookup reads as a sentence, left to right. Start from a field on the row you’re mapping, then follow it to another resource — as many times as you need:
Start from BOUGHT_BY_ORDER_ID on this gift card
→ find the order with that ID, take its CustomerId
→ find the customer with that ID, take its Shopify IDEach step has two choices:
- Source — where the step looks (order, customer, …). The step matches on the referenced record’s original ID: the same ID your source data uses to point at it.
- What to take — either a field from that record’s source data
(
CustomerIdabove), which becomes the next step’s ID to match on, or its Shopify ID, the ID your destination store assigned once that record migrated. Taking the Shopify ID is normally the last step.
Start typing in a field box and it suggests matching fields, showing each one’s value on the current sample row beside it — so you can pick a field by what it holds rather than guessing at names. A field that isn’t in the sample can still be typed in full.
Name the lookup whatever reads best — that name is how you use it in the mapping:
{
"code": CODE,
"customer_id": $._refs.customer_gid
}The Insert button drops that reference in at your cursor.
If the starting field holds a list (e.g. a product’s category IDs), the lookup resolves every entry and returns a list.
Reading one particular source
When a resource is fed by more than one source, the list names each of them — Order, meaning all of them together, and then Order — extra-orders.csv for that one file. Pick a named source when the value only exists there, or when two sources use the same IDs for different records and reading both together would be ambiguous.
This is also how a mapping reads another source of its own resource: if your orders come from your platform and from a second export, the orders mapping can look up the matching row in that second export and merge its fields in.
Start from OrderId on this order
→ find the order — extra-orders.csv with that ID, take its NoteTwo things follow from a lookup pointing at your own resource:
- The source you are currently editing is never in the list — a record can’t look itself up. If the resource has no other source, it doesn’t appear at all.
- Shopify ID isn’t offered for it. A resource migrates once, after all of its sources are prepared together, so there is no Shopify ID of a sibling source to read at that point. Take a field instead.
Looking values up in a file you upload
Some values aren’t part of any resource — marketing consent flags, an ID crosswalk from your old system, a sheet of care instructions per SKU. Uploading those as a resource would be wrong: a resource gets migrated, so you’d end up creating records you never wanted.
Upload them as reference data instead. On the migration’s Resources tab, find the Reference data card and choose Add file. Pick the file, name it, and choose its key column — the column a lookup will match against. Every value in that column has to be unique; a file with the same key twice is rejected rather than quietly dropping a row.
Once it finishes importing, the file appears in the “find the…” list of every mapping in the migration:
Start from Sku on this product
→ find the Reference: sku-metafields with that SKU, take its care_instructionsReference data is only ever read. It never becomes a resource, never gets a mapping of its own, never appears in a run, and nothing in it is created in your store.
A few things worth knowing:
- Shopify ID isn’t offered for a reference file — nothing in it is migrated, so there’s no Shopify record to point at. Take a column.
- Replacing the file keeps every lookup that reads it working. Use Replace file on the row rather than deleting and re-adding, which would make a new file that existing lookups don’t know about.
- Deleting a file leaves your mappings valid and publishable. Their lookup steps simply resolve to nothing until you upload it again.
- A run that reads a file still importing won’t start at all — it tells you which file and why, rather than running and quietly resolving everything to nothing. Extract-only runs are unaffected, since nothing reads a lookup during extract.
Fields that repeat, like line items
An order’s line items each name their own product, and every one of them
needs its own Shopify ID. Start the lookup from the field as it sits
inside the list — Items.ProductId — and the lookup offers a second
choice:
- All of them, as one list — every entry resolved, gathered into a single list. Good for something the record needs as a set.
- One per entry, matched back by ID — the lookup resolves them all and hands back a table you can read inside the list, so each line item picks up the value for its own ID:
{
"lineItems": Items.{
"quantity": Qty,
"productId": $lookup($$._refs.product_gid, $string(ProductId))
}
}The Insert button writes that form for you. Note the double $$: it
means “the row I’m mapping”, which you need because inside the list a
single $ refers to the line item you’re on.
Pick the second option whenever the value belongs to the entry rather than to the record as a whole. A list can’t do that job: if two of five products haven’t migrated yet, the list comes back with three values and nothing to say which lines they belong to. Matching by ID keeps every line item with its own product, and a line whose product isn’t there yet simply migrates without the link.
Renaming a lookup does not update expressions that already use it. If
you rename one, update the $._refs.<name> references in the mapping to
match.
Live preview
With a sample row selected, each lookup shows what it resolves to for that row, using your migration’s real extracted and migrated data — so you can confirm a chain works before publishing. A step that finds nothing shows no match. The Output and Preview tabs use the same resolved values, so what you see is what a run will produce.
A lookup can only resolve what exists: the referenced resource must be extracted (for field values) or already migrated (for Shopify IDs). Rows whose references can’t be resolved yet still migrate — the field is simply left out, and the record shows a warning you can review under the resource’s records. A later run fills the value in automatically once the referenced record has landed.
$mapLookup for computed keys
When a chain isn’t enough — say the ID to match on has to be assembled from two fields — you can resolve a single step directly inside the mapping:
{
"customer_id": $mapLookup("customer_shopify_id", $.someComputedId)
}The first argument names one step of a lookup you’ve already built. The Lookups view lists the available names under Advanced; publishing rejects anything else, and the name must be quoted rather than computed.
Ordering between resources
Building a lookup also tells Graftport that this resource depends on the one it reads: runs schedule the referenced resource ahead of the one referencing it, so chains resolve on the first pass whenever possible.
A lookup into another source of the same resource needs no such ordering. A run finishes reading every source of a resource before it prepares any of them, so whatever the other source holds is already there to merge in. A resource with several sources therefore stays on “reading” until its slowest source is done.