Magento
Magento naar Shopify migratie: de serialized EAV-val
Een Gentse modewebshop met 26 mensen verloor tijdens een Magento naar Shopify Plus migratie hun 22 best converterende variantcombinaties. Zo gebeurde het en zo loste het op.

Dag vier van de cutover. De merchandising lead van een Gentse modewebshop met 26 medewerkers legt haar best-seller rapport naast de nieuwe Shopify Plus admin. Tweeëntwintig variantcombinaties die vorig najaar de herhaalorders droegen, geven geen enkel signaal. Zelfde producten, zelfde SKU's, zelfde foto's. De fit profile badge die vroeger lage retour, terugkerende koper aangaf, staat op default voor elke maat, elke kleur, elk silhouet.
Ze belt het bureau. Het bureau belt ons. We openen de databaseback-up en vinden wat we altijd vinden bij Magento 2.4.4 winkels die hun eigen maatlogica ontwikkelden: een serialized PHP-array in een kolom waarvan de migrator nooit wist dat hij die moest lezen.
Tien dagen cutover stilgevallen. Eén kolom. Zo gebeurt het, zo herken je het, en zo voorkom je het in jouw winkel.
Het serialized graveyard in eav_attribute_option
Magento 2 bewaart attribuutopties (small, medium, large, kleurstalen en elk custom dropdown) in eav_attribute_option en eav_attribute_option_value. De eerste tabel is de optierow. De tweede bevat het leesbare label per store view. Standaard Adobe Commerce, gedocumenteerd in de Adobe Commerce EAV reference.
Wat niet standaard is: een vijf jaar oude in-house maatchart-module die een per-optie payload op dezelfde tabel had geënt, via een kolom die de oorspronkelijke ontwikkelaar in 2021 in een setup script had toegevoegd. De kolom heette additional_data. Erin stond een PHP-serialized array met signalen die het merchandising team al twee seizoenen stil had verzameld.
SELECT
eao.option_id,
ea.attribute_code,
eaov.value AS label,
eao.additional_data
FROM eav_attribute_option eao
JOIN eav_attribute_option_value eaov ON eao.option_id = eaov.option_id
JOIN eav_attribute ea ON eao.attribute_id = ea.attribute_id
WHERE ea.attribute_code IN ('size', 'fit_profile', 'silhouette')
AND eao.additional_data IS NOT NULL
ORDER BY ea.attribute_code, eao.sort_order;
De unserialized payload zag er voor elke optierow zo uit:
a:4:{
s:11:"return_rate";d:0.034;
s:8:"converts";d:0.082;
s:7:"reorder";b:1;
s:7:"profile";s:14:"low_return_fit";
}
Vermenigvuldig dat met drie attributen (maat, fit profile, silhouet) en zeventien kleur-maat grids en je hebt de 22 combinaties die de najaarskalender van merchandising aanstuurden. Niets ervan was zichtbaar in de admin UI. Het team las het via een back-office rapport dat rechtstreeks aan die kolom hing, en een frontend renderer die op basis van de payload icoontjes inzette.
Wat de migrator stilletjes deed
Het bureau koos een gerespecteerde off-the-shelf migrator. Namen blijven binnen, maar het is een van de twee waar iedereen die dit leest nu aan denkt. De tool mapt Magento 2 entiteiten op Shopify resources, draait de cutover onbeheerd en print een groen vinkje. Hij is uitstekend in wat hij doet. Wat hij niet doet, is kolommen lezen die niemand hem heeft genoemd.
De attribute-option pass van de migrator leest eav_attribute_option_value.value (het label), mapt het op een Shopify option value, en gaat verder. Alles in additional_data is onzichtbaar. Geen waarschuwing. Geen logregel. Het vinkje is groen omdat de zichtbare data netjes is overgezet.
Aan de Shopify-kant erfde elke variant zijn maat van het standaard option label. Het fit-profile attribuut, dat het stille wapen van het merchandising team was geweest, landde als één Shopify metafield met de string waarde default op elk product. De migrator schreef het omdat het attribuut bestond. Hij schreef default omdat de eigenlijke labels in eav_attribute_option_value voor dat attribuut letterlijk de string "default" waren (het team had de labels verstopt achter de frontend renderer en nooit meer bijgewerkt).
Heeft jouw Magento winkel een custom kolom op een EAV-tabel, dan leest geen enkele commerciële migrator die. De enige manier om het te weten is een SHOW CREATE TABLE op elke EAV-tabel van de bron en die diffen tegen een schone 2.4.x installatie.
Hoe we de 22 ontbrekende combinaties terugvonden
Dag vier van de cutover, het bureau had de DNS al omgezet. Terugdraaien werd een vijfcijferig gesprek met het nieuwe Shopify Plus contract en een marketingcampagne die al liep. Dus we werkten vanuit de Magento back-up vooruit.
Stap één: bouw de signal map opnieuw op uit de bron-database. De query hierboven levert de ruwe rows. De PHP-loop hieronder zet ze om in een CSV die de merchandising lead in haar eigen tools kon openen zonder op ons te wachten.
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=mage_backup', 'reader', $pw);
$sql = <<<SQL
SELECT eao.option_id, ea.attribute_code, eao.additional_data
FROM eav_attribute_option eao
JOIN eav_attribute ea ON eao.attribute_id = ea.attribute_id
WHERE eao.additional_data IS NOT NULL
SQL;
$out = fopen('signal.csv', 'w');
fputcsv($out, ['option_id', 'attribute', 'return_rate', 'converts', 'reorder', 'profile']);
foreach ($pdo->query($sql) as $row) {
$p = @unserialize($row['additional_data']);
if (!is_array($p)) continue;
fputcsv($out, [
$row['option_id'],
$row['attribute_code'],
$p['return_rate'] ?? null,
$p['converts'] ?? null,
!empty($p['reorder']) ? 1 : 0,
$p['profile'] ?? '',
]);
}
fclose($out);
Stap twee: koppel die option IDs aan de SKU's waaraan ze hingen. In Magento staat dat in catalog_product_entity_int (of _varchar, afhankelijk van het backend type van het attribuut). Die join gaf ons de ontbrekende map: welke Shopify variant ID welk signaal moest dragen.
Stap drie: bulk-write Shopify metafields met de herstelde data via de GraphQL Admin API. Drie uur scripten, acht minuten API-tijd, nul DNS-wijziging. Aan het eind van dag vijf had de merchandising lead haar badges terug en was de najaarskalender weer bruikbaar.
Het signaal op Shopify Plus opnieuw opbouwen, maar dan goed
Het team wilde pariteit met Magento, maar pariteit was het verkeerde doel. De Magento-opzet was een workaround voor een admin UI die geen vrije gestructureerde data op optierows ondersteunde. Shopify ondersteunt het wel, native, via variant metafields. De metafield documentatie van Shopify is de canonieke bron. De structuur die we kozen:
{
"namespace": "merchandising",
"key": "variant_signal",
"type": "json",
"owner_resource": "variant",
"value": "{\"return_rate\":0.034,\"converts\":0.082,\"reorder\":true,\"profile\":\"low_return_fit\"}"
}
Eén metafield per variant, getypeerd als JSON, beschikbaar voor de storefront en voor Shopify Flow. Het merchandising rapport dat vroeger op een back-office PHP-pagina draaide, is nu een bewaarde view in de Shopify admin, gefilterd op metafield-waardes. De frontend badge die icoontjes inzette is een Liquid snippet die dezelfde payload leest.
{% assign signal = product.selected_variant.metafields.merchandising.variant_signal.value %}
{% if signal.profile == 'low_return_fit' %}
<span class="badge badge-fit">True to size</span>
{% endif %}
Twee praktische opmerkingen. Eén: zet het metafield-type op json, niet json_string. De eerste wordt door Liquid geparsed en is queryable in de admin. De tweede is ondoorzichtig. Twee: registreer de definitie in Instellingen, Custom data, Variants voordat je bulk-schrijft, anders landen de waardes wel maar verschijnt er geen admin-kolom en denkt het team dat de data alsnog weg is.
Een pre-flight checklist voor elke Magento 2 cutover
Zit je nog op Magento 2.4.4 of ouder, dan ben je al voorbij Adobe's software lifecycle window. Je gaat migreren. Voor een commerciële tool jouw winkel aanraakt, doe deze vijf dingen.
Schema diff tegen stock. Draai SHOW CREATE TABLE op elke eav_*, catalog_* en sales_* tabel en diff tegen een schone Magento 2.4.x installatie. Elke extra kolom is data die de migrator overslaat.
Inventariseer elke serialized blob. Zoek in de database naar kolommen met waardes die beginnen met a:, O: of s:. Dat zijn PHP-serialized payloads. Elk daarvan is een kleine landmijn.
Map custom modules op Shopify primitives. Custom EAV hoort meestal bij variant- of product-metafields. Custom producttypes horen bij metaobjects. Custom checkout-velden horen bij cart attributes. Doe die mapping op papier voor de cutover, niet erna.
Dry-run op een sandbox. Zet een Shopify development store op, draai de migratie en draai dan je drie meest gebruikte merchandising-rapporten. Alles dat over de hele linie default teruggeeft, is een flatten.
Hou DNS 72 uur vast na het groene vinkje. Migrator-succes is geen merchandising-succes. De eerste 72 uur na een groen vinkje is het moment waarop stille flattens opduiken in de rapporten die het team daadwerkelijk gebruikt.
Wat je deze week kunt doen
Zit je middenin een migratie, draai vandaag de schema diff. Ben je post-migratie en voelt er iets niet goed in de nieuwe admin, dump de Magento back-up en grep op serialized PHP. Ben je pre-migratie, schrijf elk back-office rapport op dat geen duidelijke bron heeft in de standaard Magento UI. In die rapporten zitten de verborgen kolommen.
Toen we deze rescue voor de Gentse klant deden, kostte de fix minder tijd dan de diagnose. De diagnose kostte minder tijd dan het ontbrekende overdrachtsdocument van de oorspronkelijke ontwikkelaar zou hebben gekost. Dit is het werk dat we het vaakst doen: legacy migratie van Magento en de oudere PHP-stacks naar iets dat een klein team daadwerkelijk kan beheren. De serialized EAV-val is er één van zo'n twaalf waar we nu standaard op controleren.
Het kleinste wat je vandaag kunt doen: open één van je Magento attribuut-tabellen in een database client en kijk of er een additional_data, custom_data of extension_data kolom in staat. Bestaat hij en is hij niet leeg, dan zit er een verhaal als dit op je te wachten.
Kern
De data die Magento in z'n admin laat zien, is niet de data waar je team op leunt. Custom modules verstoppen signalen in kolommen die geen enkele off-the-shelf migrator leest.
FAQ
Waarom verdwijnt custom Magento data tijdens een Shopify migratie?
Off-the-shelf migrators lezen alleen standaard Magento kolommen. Alles wat een custom module heeft toegevoegd, zoals extra kolommen of serialized blobs, is onzichtbaar voor de mapping en wordt zonder waarschuwing weggegooid.
Kan ik de data nog terughalen als de migratie al live staat?
Ja, mits je de Magento databaseback-up hebt bewaard. Lees de bronrows, join op SKU en schrijf de herstelde waardes via de bulk API naar Shopify variant metafields. Een DNS-rollback is niet nodig.
Moet ik Shopify metafields gebruiken of een third-party app voor dit soort signalen?
Voor per-variant signalen: metafields. Ze zijn native, queryable in Liquid en Flow, en overleven theme-wissels. Apps zijn voor gedrag, niet voor datastructuur die het merchandising team bezit.
Hoe audit ik een Magento 2 winkel voor een migratie?
Draai SHOW CREATE TABLE op elke eav_, catalog_ en sales_ tabel en diff tegen een schone 2.4.x installatie. Elke extra kolom is data die de migrator overslaat. Grep daarna de dump op serialized PHP-prefixes.