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
| Feature | Direction | Notes |
|---|---|---|
| Create product | Medusa → Strapi | Full sync: title, subtitle, description, options, option values, variants, images, categories, type |
| Delete product | Medusa → Strapi | Immediate attempt, retries via queue if Strapi is down |
| Update product title / subtitle / description | Two-way | Either side can update |
| Update product categories | Medusa → Strapi | Synced on product create and update |
| Update product type | Medusa → Strapi | Synced on product create and update |
| Create product category | Medusa → Strapi | Full tree structure with parent/children |
| Update product category | Medusa → Strapi | Upserts by medusaId |
| Delete product category | Medusa → Strapi | Immediate delete from Strapi |
| Create product type | Medusa → Strapi | Triggered by product-type.created |
| Update product type | Medusa → Strapi | Triggered by product-type.updated |
| Delete product type | Medusa → Strapi | Immediate delete from Strapi |
| Create option | Medusa → Strapi | Triggered by product-option.created — includes all initial option values |
| Delete option | Medusa → Strapi | Immediate attempt, retries via queue. Strapi cascades to delete option values |
| Create option value | Medusa → Strapi | Detected on product-option.updated |
| Delete option value | Medusa → Strapi | Compares Medusa vs Strapi values, removes stale ones |
| Create variant | Medusa → Strapi | Triggered by product-variant.created, links option values |
| Delete variant | Medusa → Strapi | Immediate attempt, retries via queue |
| Update variant title / SKU / option values | Two-way | title and SKU sync both ways; option_values sync Medusa → Strapi |
| Retry failed syncs | Automatic | Immediate attempt first, then Redis queue retries up to 3 more times |
What This Integration CANNOT Do
| Limitation | Reason |
|---|---|
| Rename option values | Medusa UI doesn't support renaming — only create/delete |
| Sync images on update | By design — images are create-only to avoid overwriting Strapi media edits |
| Sync Strapi → Medusa for options | UpdateProductOptionDTO doesn't support metadata, loop prevention not possible |
| Sync option_values from Strapi → Medusa | One-way only — Medusa owns variant option assignments |
| Sync categories/type from Strapi → Medusa | One-way only — Medusa owns commerce taxonomy |
| Storefront-level Strapi content (SEO, banners, etc.) | Out of scope — this integration is product-data only |
| Real-time sync | Medusa 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
| Field | Owner | Notes |
|---|---|---|
title (product) | Shared | Either side can update |
subtitle | Shared | Either side can update |
description | Shared | Either side can update |
categories | Medusa | Synced Medusa → Strapi only |
type | Medusa | Synced Medusa → Strapi only |
title (variant) | Shared | Either side can update |
sku | Shared | Either side can update |
option_values (variant) | Medusa | Synced Medusa → Strapi only |
title (option) | Medusa | Strapi update causes one extra round-trip but no infinite loop |
| option values | Medusa | Create/delete only — no rename |
| images | Medusa | Synced 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)
└─ updateProductVariantsMetadataStepImportant: 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 Strapi4. 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 sync5. 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 retry6. 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 retry8. 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 metadataNote: 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 metadata11. 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 times13. 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 metadata14. 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 sync15. 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 times16. 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 StrapiThe 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
| Key | Purpose | Lifetime |
|---|---|---|
strapi:sync:queue | Items waiting to be processed | Until job picks up (~5 min max) |
strapi:sync:processing | Items currently being processed | Removed on success/failure. Auto-recovered if Medusa crashes |
strapi:sync:dead | Permanently failed after 4 total attempts | 1 day TTL, then auto-deleted by Redis |
Queue Item entity_type values
| entity_type | operation | Description |
|---|---|---|
product | create | Full product sync via workflow |
product_category | upsert | Category create or update |
product_type | upsert | Product type create or update |
product / product-variant / product-option / product_category / product_type | delete | Delete 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
| File | Description |
|---|---|
src/modules/strapi/index.ts | Registers the Strapi module with Medusa |
src/modules/strapi/service.ts | Core 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.ts | Validates env vars on startup, registers strapiClient in module container |
Links
| File | Description |
|---|---|
src/links/product-strapi.ts | Read-only virtual link between Medusa products and Strapi products |
Subscribers
| File | Event | Description |
|---|---|---|
src/subscribers/product-created-strapi-sync.ts | product.created | Waits 2s then runs createProductInStrapiWorkflow. Falls back to Redis queue on failure |
src/subscribers/product-updated-strapi-sync.ts | product.updated | Syncs 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.ts | product-option.created | Creates 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.ts | product-option.updated | Creates new option values, deletes removed ones, updates option title |
src/subscribers/product-variant-created-strapi-sync.ts | product-variant.created | Creates variant in Strapi for existing products only. Guards against race condition with initial product sync |
src/subscribers/product-variant-updated-strapi-sync.ts | product-variant.updated | Syncs 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.ts | product-category.created | Runs syncCategoryToStrapiWorkflow. Falls back to Redis queue on failure |
src/subscribers/product-category-updated-strapi-sync.ts | product-category.updated | Runs syncCategoryToStrapiWorkflow. Falls back to Redis queue on failure |
src/subscribers/product-category-deleted-strapi-sync.ts | product-category.deleted | Finds and deletes the category from Strapi by medusaId |
src/subscribers/product-type-created-strapi-sync.ts | product-type.created | Runs syncProductTypeToStrapiWorkflow. Falls back to Redis queue on failure |
src/subscribers/product-type-updated-strapi-sync.ts | product-type.updated | Runs syncProductTypeToStrapiWorkflow. Falls back to Redis queue on failure |
src/subscribers/product-type-deleted-strapi-sync.ts | product-type.deleted | Finds and deletes the product type from Strapi by medusaId |
Workflows
| File | Description |
|---|---|
src/workflows/create-product-in-strapi.ts | Main workflow — full product creation in Strapi including options, option values, variants, images, categories, and type |
src/workflows/create-options-in-strapi.ts | Sub-workflow — creates product options |
src/workflows/create-variants-in-strapi.ts | Sub-workflow — creates variants with option_values linked |
src/workflows/sync-category-to-strapi.ts | Upserts a product category in Strapi (create or update by medusaId) |
src/workflows/sync-product-type-to-strapi.ts | Upserts a product type in Strapi (create or update by medusaId) |
src/workflows/handle-strapi-webhook.ts | Plain async handler (not a workflow) — processes incoming Strapi webhooks and updates Medusa |
Workflow Steps
| File | Description |
|---|---|
src/workflows/steps/upload-images-to-strapi.ts | Fetches images from MinIO/S3, uploads to Strapi media library |
src/workflows/steps/create-product-in-strapi.ts | Creates product entry in Strapi with categories and type linked |
src/workflows/steps/create-options-in-strapi.ts | Creates product options in Strapi |
src/workflows/steps/create-option-values-in-strapi.ts | Creates option values and links to their option |
src/workflows/steps/create-variants-in-strapi.ts | Creates variants and links option_values |
src/workflows/steps/resolve-category-document-ids.ts | Resolves an array of Medusa category IDs to Strapi documentIds |
src/workflows/steps/resolve-type-document-id.ts | Resolves a single Medusa type_id to a Strapi documentId |
src/workflows/steps/sync-category-to-strapi.ts | Upserts a single category in Strapi, resolves parent documentId |
src/workflows/steps/sync-product-type-to-strapi.ts | Upserts a single product type in Strapi |
src/workflows/steps/update-product-metadata.ts | Saves strapi_id and strapi_document_id to product metadata |
src/workflows/steps/update-product-option-values-metadata.ts | Saves strapi_id and strapi_document_id to option value metadata |
src/workflows/steps/update-product-variants-metadata.ts | Saves strapi_id and strapi_document_id to variant metadata |
src/workflows/steps/update-in-strapi.ts | Generic update steps for product, variant, option, option value |
src/workflows/steps/prepare-strapi-update-data.ts | Parses Strapi webhook payload into typed update data |
src/workflows/steps/sync-new-option-values.ts | Finds option values without strapi_document_id and creates them |
src/workflows/steps/update-product-option-value.ts | Updates a single option value in Strapi |
src/workflows/steps/clear-product-cache.ts | Clears Medusa product cache after sync |
Workflow Hooks
| File | Hook | Description |
|---|---|---|
src/workflows/hooks/products-deleted.ts | deleteProductsWorkflow.productsDeleted | Tries immediate Strapi delete, falls back to queue on failure |
src/workflows/hooks/product-variants-deleted.ts | deleteProductVariantsWorkflow.productVariantsDeleted | Tries immediate Strapi delete, falls back to queue on failure |
src/workflows/hooks/product-options-deleted.ts | deleteProductOptionsWorkflow.productOptionsDeleted | Tries immediate Strapi delete, falls back to queue on failure |
Jobs
| File | Schedule | Description |
|---|---|---|
src/jobs/process-strapi-sync-queue.ts | Every 5 minutes | Processes 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
| File | Route | Description |
|---|---|---|
src/api/webhooks/strapi/route.ts | POST /webhooks/strapi | Receives entry.update webhooks from Strapi. Routes to handleStrapiWebhook() |
Scripts
| File | Command | Description |
|---|---|---|
src/scripts/check-queue.ts | pnpm strapi:queue | CLI 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
| Field | Type | Notes |
|---|---|---|
medusaId | Text (unique) | Medusa product ID |
title | Text | |
subtitle | Text | |
description | Rich Text | |
images | Media (multiple) | |
thumbnail | Media (single) | |
variants | Relation → oneToMany product-variant | |
options | Relation → oneToMany product-option | |
categories | Relation → manyToMany product-category | inversedBy: products |
type | Relation → manyToOne product-type | inversedBy: products |
Product Category
| Field | Type | Notes |
|---|---|---|
medusaId | Text (unique) | Medusa category ID |
name | Text | |
description | Long text | |
parent | Relation → manyToOne self | For nested category trees |
children | Relation → oneToMany self | Inverse of parent |
products | Relation → manyToMany product | mappedBy: categories |
Product Type
| Field | Type | Notes |
|---|---|---|
medusaId | Text (unique) | Medusa type ID |
value | Text | e.g. "Clothing", "Electronics" |
products | Relation → oneToMany product | mappedBy: type |
Product Variant
| Field | Type | Notes |
|---|---|---|
medusaId | Text (unique) | |
title | Text | |
sku | Text | |
product | Relation → belongsTo product | |
option_values | Relation → hasMany product-option-value |
Product Option
| Field | Type | Notes |
|---|---|---|
medusaId | Text (unique) | |
title | Text | |
product | Relation → belongsTo product | |
values | Relation → hasMany product-option-value |
Product Option Value
| Field | Type | Notes |
|---|---|---|
medusaId | Text (unique) | |
value | Text | |
option | Relation → 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')atgetAction
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 sent | Converted to | Use 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:6379Strapi Webhook Setup
In Strapi admin: Settings → Webhooks → Create
| Field | Value |
|---|---|
| URL | https://your-medusa-domain.com/webhooks/strapi |
| Events | entry.update only |
| Content types | product, 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:
- Product Categories — must exist in Strapi before products reference them
- Product Types — must exist in Strapi before products reference them
- 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
-
Option loop prevention —
UpdateProductOptionDTOdoesn't support metadata so we can't setstrapi_sync_sourceon options. A Strapi option title update causes one extra sync round-trip but does not loop infinitely. -
Option value events missing — Medusa v2 does not emit
product-option-value.createdevents. Option value creation is handled inside theproduct-option.createdsubscriber instead. -
1.5s race condition delay on variant update — When a new option is created and immediately assigned to a variant,
product-option.createdandproduct-variant.updatedfire simultaneously. The variant-updated subscriber waits 1.5s to allow option values to get theirstrapi_idsaved first. If you see missing option value links, increase this delay. -
Image updates not synced — Images are only uploaded to Strapi during product creation. Updating images in Medusa after creation will not sync to Strapi.
-
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.
-
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.
-
Dead letter items expire after 1 day — Failed items in
strapi:sync:deadare automatically deleted after 24 hours. Usepnpm strapi:queueto inspect orpnpm strapi:queue:retryto re-queue them before they expire. -
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.