Mee Platform Docs

Medusa Integration

Documentation for the Medusa v2 ↔ Strapi v5 integration in the Mee Web Platform

Medusa v2 ↔ Strapi v5 Integration Documentation

Overview

This integration keeps product data in sync between Medusa v2 (commerce backend) and Strapi v5 (content management). Medusa is the source of truth for commerce data (pricing, inventory, variants), while Strapi is used for rich content editing (descriptions, subtitles, media presentation).


What This Integration CAN Do

FeatureDirectionNotes
Create productMedusa → StrapiFull sync: title, subtitle, description, options, option values, variants, images, categories, type
Delete productMedusa → StrapiImmediate attempt, retries via queue if Strapi is down
Update product title / subtitle / descriptionTwo-wayEither side can update
Update product categoriesMedusa → StrapiSynced on product create and update
Update product typeMedusa → StrapiSynced on product create and update
Create product categoryMedusa → StrapiFull tree structure with parent/children
Update product categoryMedusa → StrapiUpserts by medusaId
Delete product categoryMedusa → StrapiImmediate delete from Strapi
Create product typeMedusa → StrapiTriggered by product-type.created
Update product typeMedusa → StrapiTriggered by product-type.updated
Delete product typeMedusa → StrapiImmediate delete from Strapi
Create optionMedusa → StrapiTriggered by product-option.created — includes all initial option values
Delete optionMedusa → StrapiImmediate attempt, retries via queue. Strapi cascades to delete option values
Create option valueMedusa → StrapiDetected on product-option.updated
Delete option valueMedusa → StrapiCompares Medusa vs Strapi values, removes stale ones
Create variantMedusa → StrapiTriggered by product-variant.created, links option values
Delete variantMedusa → StrapiImmediate attempt, retries via queue
Update variant title / SKU / option valuesTwo-waytitle and SKU sync both ways; option_values sync Medusa → Strapi
Retry failed syncsAutomaticImmediate attempt first, then Redis queue retries up to 3 more times

What This Integration CANNOT Do

LimitationReason
Rename option valuesMedusa UI doesn't support renaming — only create/delete
Sync images on updateBy design — images are create-only to avoid overwriting Strapi media edits
Sync Strapi → Medusa for optionsUpdateProductOptionDTO doesn't support metadata, loop prevention not possible
Sync option_values from Strapi → MedusaOne-way only — Medusa owns variant option assignments
Sync categories/type from Strapi → MedusaOne-way only — Medusa owns commerce taxonomy
Storefront-level Strapi content (SEO, banners, etc.)Out of scope — this integration is product-data only
Real-time syncMedusa events are async — small delay is expected

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         MEDUSA V2                               │
│                                                                 │
│  Admin Action                                                   │
│       │                                                         │
│       ▼                                                         │
│  Event Bus ──► Subscriber/Hook ──► Try immediately             │
│                                        │                        │
│                                   ┌────┴──────┐                 │
│                                   │  Success  │  ✓ done         │
│                                   │  Failure  │──► Redis Queue  │
│                                   └───────────┘       │         │
│                                               Scheduled Job     │
│                                               (every 5 min)     │
│                                                   │             │
└───────────────────────────────────────────────────┼─────────────┘
                                                    │ REST API
                                                    ▼
┌─────────────────────────────────────────────────────────────────┐
│                         STRAPI V5                               │
│                                                                 │
│   Products / Variants / Options / Option Values                 │
│   Product Categories / Product Types                           │
│                                                                 │
│   Webhook (entry.update) ──────────────────────────────────────►│
│                                          Medusa /webhooks/strapi│
└─────────────────────────────────────────────────────────────────┘

Data Ownership

FieldOwnerNotes
title (product)SharedEither side can update
subtitleSharedEither side can update
descriptionSharedEither side can update
categoriesMedusaSynced Medusa → Strapi only
typeMedusaSynced Medusa → Strapi only
title (variant)SharedEither side can update
skuSharedEither side can update
option_values (variant)MedusaSynced Medusa → Strapi only
title (option)MedusaStrapi update causes one extra round-trip but no infinite loop
option valuesMedusaCreate/delete only — no rename
imagesMedusaSynced on create only

Sync Flows

1. Product Created (Medusa → Strapi)

POST /admin/products
    │
    ▼
product.created event
    │
    ▼
Subscriber: wait 2s (DB relations commit) → run createProductInStrapiWorkflow
    │
    ├─ success → done ✓
    └─ failure → push to Redis queue with attempts: 1
                      │
                      ▼ (within 5 minutes)
                 Scheduled Job retries → up to 3 more attempts → dead letter

Workflow steps:
    ├─ useQueryGraphStep (fetch product with options, variants, images, categories, type)
    ├─ uploadImagesToStrapiStep
    ├─ resolveCategoryDocumentIdsStep (Medusa category IDs → Strapi documentIds)
    ├─ resolveTypeDocumentIdStep (Medusa type_id → Strapi documentId)
    ├─ createProductInStrapiStep (with categories + type linked)
    ├─ updateProductMetadataStep (save strapi_id, strapi_document_id)
    ├─ createOptionsInStrapiStep
    ├─ createOptionValuesInStrapiStep
    ├─ updateProductOptionValuesMetadataStep
    ├─ createVariantsInStrapiStep (with option_values linked)
    └─ updateProductVariantsMetadataStep

Important: Categories and product types must be synced to Strapi before products. If a category or type doesn't exist in Strapi yet, the relation will be silently skipped.

2. Product Deleted (Medusa → Strapi)

DELETE /admin/products/:id
    │
    ▼
deleteProductsWorkflow.productsDeleted hook
    │
    ├─ findByMedusaId → strapiService.delete(product) → success ✓
    └─ failure → push to Redis queue → retry up to 3 more times
       (Strapi product cascade deletes variants + options automatically)

3. Product Updated (Medusa → Strapi)

PATCH /admin/products/:id
    │
    ▼
product.updated event
    │
    ├─ Check strapi_sync_source === "strapi" AND flag age < 5s → skip (loop prevention)
    ├─ Sync title, subtitle, description to Strapi
    ├─ Resolve category documentIds → sync categories to Strapi
    ├─ Read type_id → resolve Strapi documentId → sync type to Strapi
    └─ Check for new options / option values → create missing ones in Strapi

4. Product Updated (Strapi → Medusa)

Strapi editor updates title/subtitle/description
    │
    ▼
Strapi webhook → POST /webhooks/strapi
    │
    ▼
handleStrapiWebhook()
    │
    ├─ Set strapi_sync_source: "strapi" + strapi_sync_at: Date.now() on product metadata
    └─ Update title/subtitle/description in Medusa
         │
         ▼
    product.updated fires → subscriber checks flag age (< 5s) → clears flag → skips outbound sync

5. Product Category Created / Updated (Medusa → Strapi)

POST/PATCH /admin/product-categories
    │
    ▼
product-category.created / product-category.updated event
    │
    ▼
syncCategoryToStrapiWorkflow
    │
    ├─ Fetch category (id, name, description, parent_category_id)
    ├─ If has parent → findByMedusaId(parent) → resolve parent documentId
    ├─ findByMedusaId(category) → exists? → update : create
    └─ success ✓ / failure → Redis queue retry

6. Product Category Deleted (Medusa → Strapi)

DELETE /admin/product-categories/:id
    │
    ▼
product-category.deleted event
    │
    └─ findByMedusaId → strapiService.delete(category) → success ✓

7. Product Type Created / Updated (Medusa → Strapi)

POST/PATCH /admin/product-types
    │
    ▼
product-type.created / product-type.updated event
    │
    ▼
syncProductTypeToStrapiWorkflow
    │
    ├─ Fetch type (id, value)
    ├─ findByMedusaId(type) → exists? → update : create
    └─ success ✓ / failure → Redis queue retry

8. Product Type Deleted (Medusa → Strapi)

DELETE /admin/product-types/:id
    │
    ▼
product-type.deleted event
    │
    └─ findByMedusaId → strapiService.delete(type) → success ✓

9. Option Created (Medusa → Strapi)

POST /admin/products/:id/options
    │
    ▼
product-option.created event
    │
    ├─ Check product has strapi_id → if not, skip (workflow handles it)
    ├─ Create option in Strapi
    ├─ Save strapi_id to product metadata strapi_options map
    └─ Create all initial option values in Strapi with strapi_id saved to metadata

Note: UpdateProductOptionDTO doesn't support metadata so option Strapi IDs are stored on the product's metadata under a strapi_options map keyed by Medusa option ID.

10. Option Value Added (Medusa → Strapi)

Edit option in Admin (add new value)
    │
    ▼
product-option.updated event
    │
    ├─ Detect new values (no strapi_document_id in metadata)
    ├─ Create option value in Strapi
    └─ Save strapi_id, strapi_document_id to option value metadata

11. Option Value Deleted (Medusa → Strapi)

Edit option in Admin (remove value)
    │
    ▼
product-option.updated event
    │
    ├─ Fetch Strapi option with populate: ["values"]
    ├─ Compare Strapi values vs current Medusa values
    └─ Delete stale Strapi values (medusaId not in Medusa anymore)

12. Option Deleted (Medusa → Strapi)

DELETE /admin/products/:id/options/:optionId
    │
    ▼
deleteProductOptionsWorkflow.productOptionsDeleted hook
    │
    ├─ findByMedusaId → strapiService.delete(option) → success ✓
    │   (Strapi cascades to delete option values automatically)
    └─ failure → push to Redis queue → retry up to 3 more times

13. Variant Created (Medusa → Strapi)

Add variant to existing product
    │
    ▼
product-variant.created event
    │
    ├─ Check product has strapi_id → if not, skip (initial workflow handles it)
    ├─ Resolve each option value's strapi_id from metadata
    ├─ Create variant in Strapi with option_values linked
    └─ Save strapi_id, strapi_document_id to variant metadata

14. Variant Updated (Two-way)

Medusa → Strapi:
PATCH /admin/products/:id/variants/:variantId
    │
    ▼
product-variant.updated
    │
    ├─ Wait 1.5s (allow option-created subscriber to finish saving strapi_ids)
    ├─ Resolve all current option values' strapi_ids
    └─ Sync title, sku, option_values to Strapi

Strapi → Medusa:
Editor updates variant title/sku → webhook → handleStrapiWebhook()
    │
    ├─ Set strapi_sync_source: "strapi" + strapi_sync_at: Date.now() on variant metadata
    └─ Update title/sku in Medusa → loop prevention skips outbound sync

15. Variant Deleted (Medusa → Strapi)

DELETE /admin/products/:id/variants/:variantId
    │
    ▼
deleteProductVariantsWorkflow.productVariantsDeleted hook
    │
    ├─ findByMedusaId → strapiService.delete(variant) → success ✓
    └─ failure → push to Redis queue → retry up to 3 more times

16. Retry Queue

Any sync failure (create, upsert, or delete):

Attempt 1 — immediate (subscriber/hook)            → fail
Attempt 2 — +5 min (job run 1, strapi:sync:queue)  → fail
Attempt 3 — +10 min (job run 2)                    → fail
Attempt 4 — +15 min (job run 3)                    → fail → strapi:sync:dead (1 day TTL)

Loop Prevention

Strapi webhook → update Medusa with { strapi_sync_source: "strapi", strapi_sync_at: <timestamp> }
    │
    ▼
product.updated / product-variant.updated fires
    │
    ├─ Flag detected AND age < 5s → remove flag → return early (no Strapi sync)
    ├─ Flag detected BUT age ≥ 5s → stale flag, clear it and proceed with sync
    └─ Flag NOT detected → normal Medusa update → sync to Strapi

The timestamp (strapi_sync_at) prevents stale flags from blocking genuine Medusa updates. A flag older than 5 seconds is treated as leftover from a previous sync cycle, not an active echo.

Note: Options do not have loop prevention because UpdateProductOptionDTO does not support metadata. A Strapi option title update will cause one extra round-trip but will NOT loop infinitely.


Redis Queue Keys

KeyPurposeLifetime
strapi:sync:queueItems waiting to be processedUntil job picks up (~5 min max)
strapi:sync:processingItems currently being processedRemoved on success/failure. Auto-recovered if Medusa crashes
strapi:sync:deadPermanently failed after 4 total attempts1 day TTL, then auto-deleted by Redis

Queue Item entity_type values

entity_typeoperationDescription
productcreateFull product sync via workflow
product_categoryupsertCategory create or update
product_typeupsertProduct type create or update
product / product-variant / product-option / product_category / product_typedeleteDelete from Strapi by medusaId

Queue CLI Commands

pnpm strapi:queue              # show status of all queues
pnpm strapi:queue:retry        # move dead items back to queue for retry (resets attempts to 0)
pnpm strapi:queue:clear-dead   # permanently delete all dead items
pnpm strapi:queue:clear-all    # wipe all queues (use with caution)

File Reference

Module

FileDescription
src/modules/strapi/index.tsRegisters the Strapi module with Medusa
src/modules/strapi/service.tsCore service — all HTTP calls to Strapi REST API (create, update, delete, findByMedusaId). Handles Strapi v5 relation format with documentId-based connect/set. DELETE handles empty 204 response body
src/modules/strapi/loaders/init-client.tsValidates env vars on startup, registers strapiClient in module container
FileDescription
src/links/product-strapi.tsRead-only virtual link between Medusa products and Strapi products

Subscribers

FileEventDescription
src/subscribers/product-created-strapi-sync.tsproduct.createdWaits 2s then runs createProductInStrapiWorkflow. Falls back to Redis queue on failure
src/subscribers/product-updated-strapi-sync.tsproduct.updatedSyncs title/subtitle/description/categories/type to Strapi. Detects and creates new options and option values. Timestamp-based loop prevention
src/subscribers/product-option-created-strapi-sync.tsproduct-option.createdCreates new option in Strapi including all its initial values. Saves option Strapi IDs to product metadata strapi_options map
src/subscribers/product-option-updated-strapi-sync.tsproduct-option.updatedCreates new option values, deletes removed ones, updates option title
src/subscribers/product-variant-created-strapi-sync.tsproduct-variant.createdCreates variant in Strapi for existing products only. Guards against race condition with initial product sync
src/subscribers/product-variant-updated-strapi-sync.tsproduct-variant.updatedSyncs title, SKU, and option_values to Strapi. Waits 1.5s for option-created to finish. Includes loop prevention
src/subscribers/product-category-created-strapi-sync.tsproduct-category.createdRuns syncCategoryToStrapiWorkflow. Falls back to Redis queue on failure
src/subscribers/product-category-updated-strapi-sync.tsproduct-category.updatedRuns syncCategoryToStrapiWorkflow. Falls back to Redis queue on failure
src/subscribers/product-category-deleted-strapi-sync.tsproduct-category.deletedFinds and deletes the category from Strapi by medusaId
src/subscribers/product-type-created-strapi-sync.tsproduct-type.createdRuns syncProductTypeToStrapiWorkflow. Falls back to Redis queue on failure
src/subscribers/product-type-updated-strapi-sync.tsproduct-type.updatedRuns syncProductTypeToStrapiWorkflow. Falls back to Redis queue on failure
src/subscribers/product-type-deleted-strapi-sync.tsproduct-type.deletedFinds and deletes the product type from Strapi by medusaId

Workflows

FileDescription
src/workflows/create-product-in-strapi.tsMain workflow — full product creation in Strapi including options, option values, variants, images, categories, and type
src/workflows/create-options-in-strapi.tsSub-workflow — creates product options
src/workflows/create-variants-in-strapi.tsSub-workflow — creates variants with option_values linked
src/workflows/sync-category-to-strapi.tsUpserts a product category in Strapi (create or update by medusaId)
src/workflows/sync-product-type-to-strapi.tsUpserts a product type in Strapi (create or update by medusaId)
src/workflows/handle-strapi-webhook.tsPlain async handler (not a workflow) — processes incoming Strapi webhooks and updates Medusa

Workflow Steps

FileDescription
src/workflows/steps/upload-images-to-strapi.tsFetches images from MinIO/S3, uploads to Strapi media library
src/workflows/steps/create-product-in-strapi.tsCreates product entry in Strapi with categories and type linked
src/workflows/steps/create-options-in-strapi.tsCreates product options in Strapi
src/workflows/steps/create-option-values-in-strapi.tsCreates option values and links to their option
src/workflows/steps/create-variants-in-strapi.tsCreates variants and links option_values
src/workflows/steps/resolve-category-document-ids.tsResolves an array of Medusa category IDs to Strapi documentIds
src/workflows/steps/resolve-type-document-id.tsResolves a single Medusa type_id to a Strapi documentId
src/workflows/steps/sync-category-to-strapi.tsUpserts a single category in Strapi, resolves parent documentId
src/workflows/steps/sync-product-type-to-strapi.tsUpserts a single product type in Strapi
src/workflows/steps/update-product-metadata.tsSaves strapi_id and strapi_document_id to product metadata
src/workflows/steps/update-product-option-values-metadata.tsSaves strapi_id and strapi_document_id to option value metadata
src/workflows/steps/update-product-variants-metadata.tsSaves strapi_id and strapi_document_id to variant metadata
src/workflows/steps/update-in-strapi.tsGeneric update steps for product, variant, option, option value
src/workflows/steps/prepare-strapi-update-data.tsParses Strapi webhook payload into typed update data
src/workflows/steps/sync-new-option-values.tsFinds option values without strapi_document_id and creates them
src/workflows/steps/update-product-option-value.tsUpdates a single option value in Strapi
src/workflows/steps/clear-product-cache.tsClears Medusa product cache after sync

Workflow Hooks

FileHookDescription
src/workflows/hooks/products-deleted.tsdeleteProductsWorkflow.productsDeletedTries immediate Strapi delete, falls back to queue on failure
src/workflows/hooks/product-variants-deleted.tsdeleteProductVariantsWorkflow.productVariantsDeletedTries immediate Strapi delete, falls back to queue on failure
src/workflows/hooks/product-options-deleted.tsdeleteProductOptionsWorkflow.productOptionsDeletedTries immediate Strapi delete, falls back to queue on failure

Jobs

FileScheduleDescription
src/jobs/process-strapi-sync-queue.tsEvery 5 minutesProcesses Redis sync queue. Crash-safe via lmove pattern. Max 4 total attempts (1 immediate + 3 job). Dead letter TTL 1 day. Supports create (product), upsert (category, type), and delete operations

API Routes

FileRouteDescription
src/api/webhooks/strapi/route.tsPOST /webhooks/strapiReceives entry.update webhooks from Strapi. Routes to handleStrapiWebhook()

Scripts

FileCommandDescription
src/scripts/check-queue.tspnpm strapi:queueCLI tool to inspect and manage the Redis sync queue

Metadata Reference

Medusa metadata is used to store Strapi IDs so we can link records across systems.

Product metadata

{
  "strapi_id": 21,
  "strapi_document_id": "yo7mro8ul1316gexx4bpqgsh",
  "strapi_sync_source": "strapi",
  "strapi_sync_at": 1741234567890,
  "strapi_options": {
    "opt_01xxx": { "strapi_id": 5, "strapi_document_id": "def456" },
    "opt_01yyy": { "strapi_id": 6, "strapi_document_id": "ghi789" }
  }
}

strapi_sync_source + strapi_sync_at are temporary — set together when Strapi writes back to Medusa via webhook. The subscriber skips outbound sync only if the flag is less than 5 seconds old. Both fields are cleared after processing.

strapi_options is a map of Medusa option ID → Strapi IDs. Used because UpdateProductOptionDTO does not support metadata, so option Strapi IDs cannot be stored on the option itself.

Option value metadata

{
  "strapi_id": 41,
  "strapi_document_id": "bopc6lm2wundpptptd1m67ua"
}

Variant metadata

{
  "strapi_id": 38,
  "strapi_document_id": "t5yvmkjydclb7nfgn1t5cf2v",
  "strapi_sync_source": "strapi",
  "strapi_sync_at": 1741234567890
}

Strapi Content Types

Product

FieldTypeNotes
medusaIdText (unique)Medusa product ID
titleText
subtitleText
descriptionRich Text
imagesMedia (multiple)
thumbnailMedia (single)
variantsRelation → oneToMany product-variant
optionsRelation → oneToMany product-option
categoriesRelation → manyToMany product-categoryinversedBy: products
typeRelation → manyToOne product-typeinversedBy: products

Product Category

FieldTypeNotes
medusaIdText (unique)Medusa category ID
nameText
descriptionLong text
parentRelation → manyToOne selfFor nested category trees
childrenRelation → oneToMany selfInverse of parent
productsRelation → manyToMany productmappedBy: categories

Product Type

FieldTypeNotes
medusaIdText (unique)Medusa type ID
valueTexte.g. "Clothing", "Electronics"
productsRelation → oneToMany productmappedBy: type

Product Variant

FieldTypeNotes
medusaIdText (unique)
titleText
skuText
productRelation → belongsTo product
option_valuesRelation → hasMany product-option-value

Product Option

FieldTypeNotes
medusaIdText (unique)
titleText
productRelation → belongsTo product
valuesRelation → hasMany product-option-value

Product Option Value

FieldTypeNotes
medusaIdText (unique)
valueText
optionRelation → belongsTo product-option

Strapi API Scaffold

Each content type requires 4 files in src/api/<name>/:

src/api/product-category/
├── content-types/product-category/schema.json
├── controllers/product-category.ts   ← factories.createCoreController(...)
├── routes/product-category.json      ← standard CRUD routes
└── services/product-category.ts      ← factories.createCoreService(...)

Without the controller/routes/service files, Strapi throws:

Cannot read properties of undefined (reading 'find') at getAction


Relation Format (Strapi v5)

Strapi v5 uses documentId (string) for all relations, not numeric id. The formatRelations method in service.ts auto-converts values:

Value sentConverted toUse case
null{ set: [] }Clear a relation
string (documentId) on relation key{ connect: [{ documentId }] }To-one relation (type, parent)
string[] (documentIds){ set: [{ documentId }] }To-many relation (categories)
number on media key{ connect: [{ id }] }Single media (thumbnail)
number[]{ connect: [{ id }] }Multi media (images)
[] empty array{ set: [] }Clear a to-many relation

Relation keys detected: type, parent, product, option (to-one); all others treated as scalars unless they are arrays.


Environment Variables

# Strapi
STRAPI_API_URL=http://localhost:1337/api
STRAPI_API_TOKEN=your-strapi-api-token
STRAPI_MEDIA_BASE_URL=https://your-minio-domain.com
STRAPI_DEFAULT_LOCALE=en

# MinIO / S3 (shared file storage)
MINIO_ENDPOINT=http://localhost:9002
MINIO_FILE_URL=http://localhost:9002/medusa-uploads
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=miniopassword
MINIO_REGION=us-east-1
MINIO_BUCKET=medusa-uploads

# Redis (already configured for Medusa)
REDIS_URL=redis://localhost:6379

Strapi Webhook Setup

In Strapi admin: Settings → Webhooks → Create

FieldValue
URLhttps://your-medusa-domain.com/webhooks/strapi
Eventsentry.update only
Content typesproduct, product-variant, product-option, product-option-value

The webhook payload must contain a medusaId field in the entry — this is how we match Strapi records back to Medusa. Categories and product types are not included in webhooks since they are Medusa → Strapi only.


Sync Order for Initial Setup

When setting up or bulk-importing data, always sync in this order:

  1. Product Categories — must exist in Strapi before products reference them
  2. Product Types — must exist in Strapi before products reference them
  3. Products — categories and types are resolved by medusaId at sync time

If a product is synced before its category/type exists in Strapi, the relation will be null in Strapi. Re-triggering a product update after categories/types are synced will fix it.


Known Limitations & Future Improvements

  1. Option loop prevention — UpdateProductOptionDTO doesn't support metadata so we can't set strapi_sync_source on options. A Strapi option title update causes one extra sync round-trip but does not loop infinitely.

  2. Option value events missing — Medusa v2 does not emit product-option-value.created events. Option value creation is handled inside the product-option.created subscriber instead.

  3. 1.5s race condition delay on variant update — When a new option is created and immediately assigned to a variant, product-option.created and product-variant.updated fire simultaneously. The variant-updated subscriber waits 1.5s to allow option values to get their strapi_id saved first. If you see missing option value links, increase this delay.

  4. Image updates not synced — Images are only uploaded to Strapi during product creation. Updating images in Medusa after creation will not sync to Strapi.

  5. No bulk sync — There is no tool to sync all existing products/categories/types to Strapi at once. Only newly created or updated records are synced automatically.

  6. Scheduled job requires Medusa running — The retry queue only processes while Medusa is running. Items in the queue survive restarts (stored in Redis) and will be processed when Medusa comes back up.

  7. Dead letter items expire after 1 day — Failed items in strapi:sync:dead are automatically deleted after 24 hours. Use pnpm strapi:queue to inspect or pnpm strapi:queue:retry to re-queue them before they expire.

  8. Category tree depth — Parent categories must be synced before their children. The sync step resolves the parent's Strapi documentId at sync time — if the parent hasn't been synced yet, the parent relation will be null.

On this page