Shared fields
Every staging model inherits from a base that adds a small set of fields available on every resource. Document once, use everywhere.
Identity & tracing
How Graftport links a staged record back to its source row.
original_idstringrequiredThe source platform’s stable ID for this row. Always emit it from your mapping — without it Graftport can’t reconcile re-extracts.
It’s automatically converted to a custom.original_id metafield on
the destination record so future runs can match the row back.
"original_id": entity_idsource_url_pathstringThe URL path the row had on the source platform. Used by the
redirect resource to set up
old → new URL maps. Becomes a custom.source_url_path metafield.
"source_url_path": "/" & url_key & ".html"idstringThe Shopify ID for this record. Almost never set in mapping —
Graftport resolves IDs at load time using original_id. Set
explicitly only if you’re upserting a record whose Shopify ID you
already know.
excludebooleandefault falseSet to true to skip this row entirely — no staged record is
produced, nothing reaches Shopify. Useful for filtering out test
data, draft rows, or anything the merchant doesn’t want migrated.
"exclude": status = "draft" or sku = ""Custom metafields
Pour any merchant-specific attribute into a metafield. Type is auto-detected unless you override.
dict_metafieldsobjectA flat dict of metafields. Keys become metafield keys; values become metafield values, with the type auto-detected from the value:
| Value type | Auto-detected metafield type |
|---|---|
| string | single_line_text_field (or multi_line_text_field if it contains newlines) |
| int | number_integer |
| float | number_decimal |
| bool | boolean |
| list / dict | json |
Empty values (null, "", [], {}) are silently skipped — no
metafield is created.
"dict_metafields": {
"warranty_years": warranty_years,
"fragile": fragile = "yes",
"specs": { "weight_kg": weight, "color": color }
}To force a specific Shopify type, use the {value, type} form:
"dict_metafields": {
"rich_description": {
"value": rich_html,
"type": "rich_text_field"
},
"expiry": {
"value": expires_at,
"type": "date"
}
}metafield_prefixstringdefault customThe namespace used for every entry in dict_metafields. Defaults to
custom. Override per resource when you want a different namespace
(e.g. magento to mark fields that came from Magento).
"metafield_prefix": "magento"The two reserved metafields (original_id, source_url_path) ignore
this — they always go in custom.
Shopify metafield types
When you use the {value, type} form in dict_metafields, the type
must be one of Shopify’s supported metafield types. Common ones:
| Type | Value format | Use for |
|---|---|---|
single_line_text_field | string | Short text. |
multi_line_text_field | string | Long text or any value with line breaks. |
rich_text_field | string of Shopify rich-text JSON | HTML / formatted descriptions. |
number_integer | integer | Counts, IDs. |
number_decimal | string decimal | Prices, ratings. |
boolean | true / false | Flags. |
date | YYYY-MM-DD | Calendar dates. |
date_time | ISO8601 | Timestamps. |
url | string URL | External links. |
color | #rrggbb | Hex colors. |
weight | JSON {value, unit} | Weights with unit. |
dimension | JSON {value, unit} | Dimensions. |
json | any JSON | Free-form structured data. |
list.single_line_text_field | JSON array of strings | Tag-like lists. |
list.product_reference | JSON array of product GIDs | Related products. |
Shopify’s full list lives in their metafield types documentation .
Patterns
Conditional metafields
"dict_metafields": {
"warranty_years": warranty_years > 0 ? warranty_years : null,
"fragile": fragile,
"lead_time_days": lead_time
}null/empty values are dropped automatically — no need to wrap each
in a conditional unless the JSONata expression itself would error on
missing fields.
Looping a source attribute array into metafields
"dict_metafields": $reduce(
custom_attributes,
function($acc, $attr) {
$merge([$acc, { $attr.code: $attr.value }])
},
{}
)Mixing auto-detected and forced types
"dict_metafields": {
"instructions": care_instructions,
"color": { "value": color_hex, "type": "color" },
"ships_after": { "value": available_from, "type": "date" }
}Gotchas
original_id is doing real work. Don’t drop it from your
mapping — Graftport uses it to keep extracts and loads idempotent.
Without it, every re-run treats every row as new.
exclude: truedoesn’t delete anything previously loaded. It just stops new staging records being produced. To remove an already-loaded record, use the migration’s purge tooling.- Metafield keys must be 2-64 chars and use snake_case. Keys with spaces or special chars are rejected by Shopify.
dict_metafieldsvalues are skipped if empty — a key withnull,"",[], or{}produces no metafield at all. This is usually what you want; if you genuinely need to set an empty value, use the{value, type}form with a non-empty placeholder.- Auto-detection picks
multi_line_text_fieldfor any string with newlines. If you want plainsingle_line_text_fieldfor a string that might contain newlines, force the type.