Magento's flexibility — and most of the work in any Magento → Shopify
replatform — comes from EAV (entity-attribute-value) storage. Every
custom product attribute, every customer attribute, every category
attribute is a row in EAV. Shopify does not have EAV. It has
variants (a fixed-shape, finite-value option model) and
metafields (typed key-value pairs attached to any record).
This guide is the practical contract for how Graftport's mapping
editor turns the first into the second, and what to verify on
staging.
The decision tree
For each Magento product attribute, the mapping editor evaluates,
in order:
- Is it a system Shopify field? (
title, body_html, vendor,
product_type, tags, handle, seo_title, seo_description.)
If yes, map to that Shopify field directly.
- Is it an option that becomes a variant axis? (Size, Color,
Material — finite, enumerable values, used in product
configuration.) If yes, lift into Shopify variants.
- Otherwise, store as a metafield under a stable namespace.
The default mapping seeded by the wizard already covers (1) and (2)
for the standard Magento attribute set. Step (3) is the part you
review per-store, because every Magento install accumulates custom
attributes nobody fully documented.
The metafield contract
Graftport's default for a Magento attribute that does not fit a
system field or a variant becomes a Shopify metafield with this
shape:
| Property | Default value |
|---|
| Namespace | magento (configurable per-mapping) |
| Key | The Magento attribute code, lowercased |
| Type | Inferred from the Magento attribute's input type |
| Description | The Magento attribute's frontend label |
So a Magento attribute coded kit_month with input type text
lands as magento.kit_month of metafield type
single_line_text_field. A material_origin attribute with input
type select lands as magento.material_origin of type
single_line_text_field too — Graftport stores the option label
as the value, not the option ID, because option IDs are
Magento-internal noise that Shopify cannot resolve.
The point of holding onto the original attribute code: if your
Magento theme references getAttribute('kit_month') somewhere, your
new Shopify theme can reference metafields.magento.kit_month and
get the equivalent value out. Direct one-to-one translation,
storefront-side.
Magento input type → Shopify metafield type
| Magento input type | Shopify metafield type |
|---|
| Text Field | single_line_text_field |
| Text Area | multi_line_text_field |
| Yes/No | boolean |
| Date | date |
| Datetime | date_time |
| Price | money (currency from store) |
| Decimal | number_decimal |
| Integer | number_integer |
| Dropdown / Select | single_line_text_field (label) |
| Multi-Select | list.single_line_text_field |
| Media Image | file_reference (asset uploaded) |
| Visual Swatch | single_line_text_field (label) |
| Text Swatch | single_line_text_field (label) |
When Graftport meets an attribute it cannot type cleanly (typically
a custom plugin's input type), it falls back to
single_line_text_field and stores the raw string value. The
mapping editor flags this with a warning so you can override.
Editing the mapping for one attribute
From your migration's home screen, click Resources → Products →
Edit mapping. The mapping editor opens with three columns:
- Source schema (left). Every attribute Graftport found on a
sample of your Magento products. Hover any row to see the
inferred type and a sample value.
- Mapping (middle). The current rule for each attribute:
what Shopify field it lands on, with what transform.
- Destination schema (right). Shopify's product schema with
metafield slots highlighted by namespace.
To override an attribute's mapping:
- Click the attribute on the left.
- The mapping rule drawer slides in with the current rule.
- Change the destination type: pick a different metafield
namespace, switch from metafield to a system field, or to
"drop".
- Optionally apply a transform: prefix, lowercase, parse-to-
number, etc. Graftport ships a small library of common
transforms; you can also write a SQL expression for anything
custom.
- Click Save. The change is versioned in
app.mapping_template_version.
Dry-running the mapping change
Before re-loading, dry-run the change:
- Go back to the migration's home screen.
- Start a run → Mode:
Transform only → Dry-run on.
- The transform run reuses the last extract's data and applies
the new mapping. No source-side reads, no destination writes.
- Open Data tab on the run, pick a representative product,
and confirm the metafield came out as expected.
When the transformed shape looks right, run a full load (dry-run
off). Already-loaded products are re-synced at the (cheaper)
sync rate; the metafield change lands without touching anything
else.
Mapping decisions that actually matter
Most attributes fall on the seeded defaults and never need a
hand. The ones that do, in order of how often we hit them:
- Magento attributes used in cart price rules. Rules like
"10% off if
customer_group = wholesale" need the customer-side
attribute migrated. Move it to customers.metafields.<your-ns>.<key>
and rebuild the equivalent rule as a Shopify automatic discount
using the metafield value.
- Free-form visual swatches that store image filenames. The
default lands the filename as a string. If you want the image
itself uploaded to Shopify Files and referenced by ID, switch
the destination type to
file_reference. Graftport handles the
upload during transform.
- Multi-store-specific attributes. A Magento attribute can have
per-store-view values. Pick the store view you are migrating
(in the wizard notes) and Graftport reads only that view's
values. If you want both, fork the migration and run two.
- Attributes never used. The mapping editor's source-schema
panel shows which attributes are actually populated on at least
one product. If a custom attribute is on zero products, set its
mapping rule to drop. Cleaner storefront, fewer metafields
to wade through later.
The no-op trap
A common mistake: Magento has the attribute, the migration moves
the metafield, but the new Shopify theme never reads the
metafield. Two ways to verify:
- The metafield is on the product. Open the staging Shopify
Admin → Products → click a product → scroll to the metafields
section. Confirm
magento.<attribute_code> is listed with the
expected value.
- The theme renders it. Most Online Store 2.0 themes need an
explicit "use this metafield" config. Open Shopify Admin →
Online Store → Customize → pick the relevant section → bind a
block to the metafield. If your theme is custom, the dev
reference is
{{ product.metafields.magento.<key> }}.
If the metafield is on the product but not on the storefront, the
migration is correct — the theme is the gap.
What we do not migrate as metafields
A few Magento attribute types never become metafields, by
design:
url_key. Becomes the Shopify product handle directly. The
url_rewrite table becomes 301 redirects on the destination
Shopify store under Online Store → Navigation → URL Redirects.
status (1/2 enabled/disabled). Disabled products are not
migrated. There is no equivalent Shopify metafield; "available
on storefront" is a Shopify product status field.
tax_class_id. Becomes the Shopify product's
tax-collected/exempt flag plus the destination store's tax
zones, not a metafield. Tax in Shopify is configured per-region,
not per-product-attribute.
weight and dimensions. Land in Shopify's native shipping
fields (variant weight + product dimension metafields under the
reserved shipping namespace).
Re-running after a mapping change
Mappings are versioned. Editing a mapping creates a new
mapping_template_version. The migration uses whichever version
you have set as current. Re-running the migration after a mapping
change re-transforms every record that was previously loaded with
the old mapping and re-syncs the destination.
This is how you fix a metafield mistake on day three without
re-pushing the catalogue from scratch: edit the mapping, dry-run
transform, re-run with load. Records that already match the new
shape are skipped.
Related reading
When you are ready to drill into your own attribute set, head to
Resources → Products → Edit mapping in your migration. Most
overrides land in under five minutes.