@mercurjs/docs 2.2.1 → 2.3.0-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/content/home.mdx +107 -0
  2. package/content/learn/architecture.mdx +168 -83
  3. package/content/learn/introduction.mdx +161 -56
  4. package/content/learn/migration-to-2-0.mdx +187 -0
  5. package/content/learn/why-mercur.mdx +91 -0
  6. package/content/platform/attribute/concepts/attribute-types.mdx +59 -0
  7. package/content/platform/attribute/concepts/global-vs-inline.mdx +50 -0
  8. package/content/platform/attribute/concepts/variant-axes.mdx +61 -0
  9. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +69 -0
  10. package/content/platform/attribute/guides/create-a-variant-axis.mdx +83 -0
  11. package/content/platform/attribute/guides/create-an-attribute.mdx +81 -0
  12. package/content/platform/attribute/overview.mdx +87 -0
  13. package/content/platform/attribute/reference/data-models.mdx +56 -0
  14. package/content/platform/attribute/reference/events.mdx +41 -0
  15. package/content/platform/attribute/reference/links.mdx +45 -0
  16. package/content/platform/attribute/reference/service.mdx +49 -0
  17. package/content/platform/attribute/reference/workflows.mdx +45 -0
  18. package/content/platform/catalog/concepts/master-products.mdx +54 -0
  19. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +48 -0
  20. package/content/platform/catalog/concepts/status-lifecycle.mdx +58 -0
  21. package/content/platform/catalog/concepts/variants-categories-collections.mdx +49 -0
  22. package/content/platform/catalog/guides/allowlist-stores.mdx +59 -0
  23. package/content/platform/catalog/guides/create-a-master-product.mdx +70 -0
  24. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +79 -0
  25. package/content/platform/catalog/overview.mdx +90 -0
  26. package/content/platform/catalog/reference/data-models.mdx +66 -0
  27. package/content/platform/catalog/reference/events.mdx +41 -0
  28. package/content/platform/catalog/reference/links.mdx +44 -0
  29. package/content/platform/catalog/reference/service.mdx +52 -0
  30. package/content/platform/catalog/reference/workflows.mdx +40 -0
  31. package/content/platform/commission/concepts/order-commission-lines.mdx +65 -0
  32. package/content/platform/commission/concepts/rule-matching.mdx +86 -0
  33. package/content/platform/commission/concepts/rules-and-rates.mdx +84 -0
  34. package/content/platform/commission/guides/batch-update-rules.mdx +58 -0
  35. package/content/platform/commission/guides/create-a-rate.mdx +72 -0
  36. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +55 -0
  37. package/content/platform/commission/overview.mdx +86 -0
  38. package/content/platform/commission/reference/data-models.mdx +75 -0
  39. package/content/platform/commission/reference/events.mdx +58 -0
  40. package/content/platform/commission/reference/links.mdx +45 -0
  41. package/content/platform/commission/reference/service.mdx +51 -0
  42. package/content/platform/commission/reference/workflows.mdx +40 -0
  43. package/content/platform/offer/concepts/pricing-and-inventory.mdx +72 -0
  44. package/content/platform/offer/concepts/shipping.mdx +48 -0
  45. package/content/platform/offer/concepts/what-is-an-offer.mdx +65 -0
  46. package/content/platform/offer/guides/bulk-create-offers.mdx +84 -0
  47. package/content/platform/offer/guides/create-an-offer.mdx +83 -0
  48. package/content/platform/offer/guides/manage-offer-inventory.mdx +53 -0
  49. package/content/platform/offer/overview.mdx +84 -0
  50. package/content/platform/offer/reference/data-models.mdx +56 -0
  51. package/content/platform/offer/reference/events.mdx +39 -0
  52. package/content/platform/offer/reference/links.mdx +60 -0
  53. package/content/platform/offer/reference/service.mdx +53 -0
  54. package/content/platform/offer/reference/workflows.mdx +38 -0
  55. package/content/platform/order-group/concepts/computed-totals.mdx +59 -0
  56. package/content/platform/order-group/concepts/order-splitting.mdx +61 -0
  57. package/content/platform/order-group/concepts/the-order-group.mdx +64 -0
  58. package/content/platform/order-group/guides/list-order-groups.mdx +67 -0
  59. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +63 -0
  60. package/content/platform/order-group/guides/split-a-cart.mdx +58 -0
  61. package/content/platform/order-group/overview.mdx +83 -0
  62. package/content/platform/order-group/reference/data-models.mdx +40 -0
  63. package/content/platform/order-group/reference/events.mdx +37 -0
  64. package/content/platform/order-group/reference/links.mdx +45 -0
  65. package/content/platform/order-group/reference/service.mdx +50 -0
  66. package/content/platform/order-group/reference/workflows.mdx +39 -0
  67. package/content/platform/payout/concepts/account-lifecycle.mdx +75 -0
  68. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +70 -0
  69. package/content/platform/payout/concepts/payout-pipeline.mdx +99 -0
  70. package/content/platform/payout/guides/create-a-payout-account.mdx +59 -0
  71. package/content/platform/payout/guides/process-a-provider-webhook.mdx +74 -0
  72. package/content/platform/payout/guides/start-provider-onboarding.mdx +50 -0
  73. package/content/platform/payout/overview.mdx +86 -0
  74. package/content/platform/payout/reference/data-models.mdx +61 -0
  75. package/content/platform/payout/reference/events.mdx +48 -0
  76. package/content/platform/payout/reference/links.mdx +36 -0
  77. package/content/platform/payout/reference/service.mdx +53 -0
  78. package/content/platform/payout/reference/workflows.mdx +32 -0
  79. package/content/platform/product-edit/concepts/change-actions.mdx +66 -0
  80. package/content/platform/product-edit/concepts/change-pipeline.mdx +69 -0
  81. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +66 -0
  82. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +76 -0
  83. package/content/platform/product-edit/guides/edit-a-product.mdx +74 -0
  84. package/content/platform/product-edit/guides/request-a-revision.mdx +54 -0
  85. package/content/platform/product-edit/overview.mdx +85 -0
  86. package/content/platform/product-edit/reference/data-models.mdx +65 -0
  87. package/content/platform/product-edit/reference/events.mdx +47 -0
  88. package/content/platform/product-edit/reference/links.mdx +39 -0
  89. package/content/platform/product-edit/reference/service.mdx +43 -0
  90. package/content/platform/product-edit/reference/workflows.mdx +49 -0
  91. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +63 -0
  92. package/content/platform/review/concepts/ratings-and-moderation.mdx +64 -0
  93. package/content/platform/review/concepts/the-review-model.mdx +56 -0
  94. package/content/platform/review/guides/compute-aggregate-ratings.mdx +57 -0
  95. package/content/platform/review/guides/create-a-review.mdx +55 -0
  96. package/content/platform/review/guides/moderate-a-review.mdx +58 -0
  97. package/content/platform/review/guides/respond-to-a-review.mdx +61 -0
  98. package/content/platform/review/overview.mdx +87 -0
  99. package/content/platform/review/reference/data-models.mdx +36 -0
  100. package/content/platform/review/reference/events.mdx +61 -0
  101. package/content/platform/review/reference/links.mdx +43 -0
  102. package/content/platform/review/reference/service.mdx +54 -0
  103. package/content/platform/review/reference/workflows.mdx +31 -0
  104. package/content/platform/store/concepts/lifecycle.mdx +62 -0
  105. package/content/platform/store/concepts/store-entity.mdx +53 -0
  106. package/content/platform/store/concepts/team.mdx +50 -0
  107. package/content/platform/store/guides/create-a-store.mdx +55 -0
  108. package/content/platform/store/guides/manage-the-team.mdx +55 -0
  109. package/content/platform/store/guides/moderate-a-store.mdx +59 -0
  110. package/content/platform/store/overview.mdx +86 -0
  111. package/content/platform/store/reference/data-models.mdx +89 -0
  112. package/content/platform/store/reference/events.mdx +43 -0
  113. package/content/platform/store/reference/links.mdx +71 -0
  114. package/content/platform/store/reference/service.mdx +51 -0
  115. package/content/platform/store/reference/workflows.mdx +35 -0
  116. package/content/references/api/admin/commission-rates/create-commission-rate.mdx +1 -1
  117. package/content/references/api/admin/commission-rates/list-commission-rates.mdx +2 -2
  118. package/content/references/api/admin/commission-rates/update-commission-rate.mdx +1 -1
  119. package/content/references/api/admin/offers/batch-create-offers.mdx +3 -3
  120. package/content/references/api/admin/order-groups/list-order-groups.mdx +1 -1
  121. package/content/references/api/admin/product-attributes/create-attribute-value.mdx +2 -2
  122. package/content/references/api/admin/product-attributes/create-product-attribute.mdx +1 -1
  123. package/content/references/api/admin/product-attributes/update-product-attribute.mdx +1 -1
  124. package/content/references/api/admin/product-changes/confirm-product-change.mdx +1 -1
  125. package/content/references/api/admin/products/batch-product-attributes.mdx +1 -1
  126. package/content/references/api/admin/products/create-product.mdx +1 -1
  127. package/content/references/api/admin/products/preview-product.mdx +1 -1
  128. package/content/references/api/admin.mdx +4 -5
  129. package/content/references/api/conventions.mdx +9 -7
  130. package/content/references/api/store/carts/add-line-item.mdx +1 -1
  131. package/content/references/api/store/offers/list-offers.mdx +1 -1
  132. package/content/references/api/store/order-groups/list-order-groups.mdx +1 -1
  133. package/content/references/api/store.mdx +4 -13
  134. package/content/references/api/vendor/members/accept-member-invite.mdx +1 -1
  135. package/content/references/api/vendor/offers/batch-create-offers.mdx +4 -0
  136. package/content/references/api/vendor/offers/batch-offer-inventory-items.mdx +2 -0
  137. package/content/references/api/vendor/offers/create-offer.mdx +13 -1
  138. package/content/references/api/vendor/offers/list-offers.mdx +4 -0
  139. package/content/references/api/vendor/offers/retrieve-offer.mdx +4 -0
  140. package/content/references/api/vendor/offers/update-offer.mdx +12 -0
  141. package/content/references/api/vendor/payout-accounts/create-onboarding.mdx +1 -1
  142. package/content/references/api/vendor/products/batch-product-attributes.mdx +1 -1
  143. package/content/references/api/vendor/products/create-product-variant.mdx +1 -1
  144. package/content/references/api/vendor/products/create-product.mdx +1 -1
  145. package/content/references/api/vendor/products/delete-product.mdx +1 -1
  146. package/content/references/api/vendor/products/update-product.mdx +1 -1
  147. package/content/references/api/vendor/sellers/create-seller.mdx +2 -2
  148. package/content/references/api/vendor/sellers/list-sellers.mdx +1 -1
  149. package/content/references/api/vendor.mdx +6 -5
  150. package/content/references/configuration.mdx +16 -33
  151. package/content/references/overview.mdx +34 -52
  152. package/content/references/panel-extensions/create-page.mdx +194 -0
  153. package/content/references/panel-extensions/custom-fields.mdx +256 -0
  154. package/content/references/panel-extensions/overview.mdx +102 -0
  155. package/content/references/panel-extensions/widgets.mdx +212 -0
  156. package/content/resources/ai/mcp.mdx +2 -2
  157. package/content/resources/ai/overview.mdx +21 -16
  158. package/content/resources/ai/skills.mdx +67 -0
  159. package/content/resources/best-practices/api-routes.mdx +55 -43
  160. package/content/resources/best-practices/custom-fields.mdx +116 -92
  161. package/content/resources/best-practices/frontend.mdx +62 -50
  162. package/content/resources/best-practices/module-links.mdx +48 -34
  163. package/content/resources/best-practices/modules.mdx +53 -27
  164. package/content/resources/best-practices/overview.mdx +45 -17
  165. package/content/resources/best-practices/subscribers-and-jobs.mdx +37 -24
  166. package/content/resources/best-practices/types.mdx +38 -23
  167. package/content/resources/best-practices/workflows.mdx +33 -21
  168. package/content/resources/customization/custom-fields.mdx +15 -15
  169. package/content/resources/customization/extend-a-workflow.mdx +7 -4
  170. package/content/resources/customization/extending-panels.mdx +55 -52
  171. package/content/resources/deployment/medusa-cloud.mdx +21 -20
  172. package/content/resources/deployment/self-host.mdx +123 -0
  173. package/content/resources/integrations/overview.mdx +38 -0
  174. package/content/resources/integrations/stripe-connect.mdx +39 -38
  175. package/content/resources/tutorials/add-a-block.mdx +25 -18
  176. package/content/resources/tutorials/add-a-widget.mdx +32 -23
  177. package/content/resources/tutorials/add-order-detail-button.mdx +33 -20
  178. package/content/resources/tutorials/attributes-and-variant-axes.mdx +28 -27
  179. package/content/resources/tutorials/build-a-block.mdx +26 -15
  180. package/content/resources/tutorials/custom-api-route.mdx +32 -20
  181. package/content/resources/tutorials/custom-panel-page.mdx +21 -12
  182. package/content/resources/tutorials/customize-navigation.mdx +30 -23
  183. package/content/resources/tutorials/extend-forms-and-tables.mdx +36 -28
  184. package/content/resources/tutorials/extend-onboarding.mdx +38 -35
  185. package/content/resources/tutorials/master-products-and-offers.mdx +28 -23
  186. package/content/telemetry.mdx +3 -3
  187. package/content/user-guide/admin/attributes/how-tos/create-an-attribute.mdx +64 -0
  188. package/content/user-guide/admin/attributes/how-tos/manage-possible-values.mdx +40 -0
  189. package/content/user-guide/admin/attributes/overview.mdx +22 -0
  190. package/content/user-guide/admin/commissions/how-tos/create-a-commission-rule.mdx +63 -0
  191. package/content/user-guide/admin/commissions/how-tos/edit-the-global-commission.mdx +48 -0
  192. package/content/user-guide/admin/commissions/how-tos/manage-a-commission-rule.mdx +45 -0
  193. package/content/user-guide/admin/commissions/overview.mdx +25 -0
  194. package/content/user-guide/admin/overview.mdx +20 -12
  195. package/content/user-guide/admin/product-requests/how-tos/review-a-new-product.mdx +58 -0
  196. package/content/user-guide/admin/product-requests/how-tos/review-a-product-edit.mdx +48 -0
  197. package/content/user-guide/admin/product-requests/overview.mdx +25 -0
  198. package/content/user-guide/vendor/offers/how-tos/create-an-offer.mdx +59 -0
  199. package/content/user-guide/vendor/offers/how-tos/update-prices-and-stock.mdx +40 -0
  200. package/content/user-guide/vendor/offers/overview.mdx +22 -0
  201. package/content/user-guide/vendor/onboarding.mdx +79 -0
  202. package/content/user-guide/vendor/orders/how-tos/fulfill-an-order.mdx +49 -0
  203. package/content/user-guide/vendor/orders/how-tos/mark-an-order-as-delivered.mdx +33 -0
  204. package/content/user-guide/vendor/orders/how-tos/process-a-return.mdx +42 -0
  205. package/content/user-guide/vendor/orders/how-tos/refund-an-order.mdx +38 -0
  206. package/content/user-guide/vendor/orders/how-tos/ship-an-order.mdx +40 -0
  207. package/content/user-guide/vendor/orders/overview.mdx +31 -0
  208. package/content/user-guide/vendor/overview.mdx +23 -12
  209. package/content/user-guide/vendor/products/how-tos/edit-a-product.mdx +44 -0
  210. package/content/user-guide/vendor/products/how-tos/submit-a-product.mdx +63 -0
  211. package/content/user-guide/vendor/products/overview.mdx +22 -0
  212. package/llms.txt +176 -142
  213. package/package.json +1 -1
  214. package/content/learn/concepts.mdx +0 -84
  215. package/content/learn/installation.mdx +0 -117
  216. package/content/learn/mirakl-alternative.mdx +0 -86
  217. package/content/migration/from-1-x-to-2-0.mdx +0 -152
  218. package/content/migration/from-2-0-to-2-1.mdx +0 -105
  219. package/content/migration/overview.mdx +0 -58
  220. package/content/references/api/store/search/search.mdx +0 -136
  221. package/content/references/modules/commission.mdx +0 -106
  222. package/content/references/modules/custom-fields.mdx +0 -45
  223. package/content/references/modules/media.mdx +0 -55
  224. package/content/references/modules/offer.mdx +0 -64
  225. package/content/references/modules/payout.mdx +0 -121
  226. package/content/references/modules/product-attribute.mdx +0 -111
  227. package/content/references/modules/product-edit.mdx +0 -80
  228. package/content/references/modules/search.mdx +0 -112
  229. package/content/references/modules/seller.mdx +0 -175
  230. package/content/references/panel-extension-api.mdx +0 -337
  231. package/content/references/workflows/cart/add-seller-shipping-method-to-cart.mdx +0 -48
  232. package/content/references/workflows/cart/complete-cart-with-split-orders.mdx +0 -36
  233. package/content/references/workflows/cart/list-seller-shipping-options-for-cart.mdx +0 -39
  234. package/content/references/workflows/cart/update-cart-seller-promotions.mdx +0 -42
  235. package/content/references/workflows/commission/batch-commission-rules.mdx +0 -49
  236. package/content/references/workflows/commission/create-commission-rates.mdx +0 -41
  237. package/content/references/workflows/commission/delete-commission-rates.mdx +0 -34
  238. package/content/references/workflows/commission/refresh-order-commission-lines.mdx +0 -30
  239. package/content/references/workflows/commission/update-commission-rates.mdx +0 -35
  240. package/content/references/workflows/media/set-category-images.mdx +0 -41
  241. package/content/references/workflows/media/set-collection-images.mdx +0 -41
  242. package/content/references/workflows/member/accept-member-invite.mdx +0 -33
  243. package/content/references/workflows/member/add-seller-member.mdx +0 -30
  244. package/content/references/workflows/member/create-member-invites.mdx +0 -34
  245. package/content/references/workflows/member/delete-member-invite.mdx +0 -24
  246. package/content/references/workflows/member/remove-seller-member.mdx +0 -28
  247. package/content/references/workflows/member/resend-member-invite.mdx +0 -28
  248. package/content/references/workflows/member/update-member-role.mdx +0 -28
  249. package/content/references/workflows/member/update-member.mdx +0 -35
  250. package/content/references/workflows/offer/batch-offer-inventory-items.mdx +0 -62
  251. package/content/references/workflows/offer/create-offers.mdx +0 -65
  252. package/content/references/workflows/offer/delete-offers.mdx +0 -35
  253. package/content/references/workflows/offer/update-offers.mdx +0 -53
  254. package/content/references/workflows/order/cancel-order-fulfillment.mdx +0 -34
  255. package/content/references/workflows/order/confirm-claim-request.mdx +0 -25
  256. package/content/references/workflows/order/confirm-exchange-request.mdx +0 -25
  257. package/content/references/workflows/order/confirm-order-edit-request.mdx +0 -25
  258. package/content/references/workflows/order/confirm-return-receive.mdx +0 -25
  259. package/content/references/workflows/order/create-order-fulfillment.mdx +0 -45
  260. package/content/references/workflows/order-group/get-order-group-detail.mdx +0 -29
  261. package/content/references/workflows/order-group/get-order-groups-list.mdx +0 -38
  262. package/content/references/workflows/overview.mdx +0 -72
  263. package/content/references/workflows/payout/create-onboarding.mdx +0 -36
  264. package/content/references/workflows/payout/create-payout-account.mdx +0 -33
  265. package/content/references/workflows/payout/create-payout.mdx +0 -30
  266. package/content/references/workflows/payout/process-payout-for-webhook.mdx +0 -34
  267. package/content/references/workflows/product/confirm-products.mdx +0 -48
  268. package/content/references/workflows/product/create-products.mdx +0 -63
  269. package/content/references/workflows/product/link-sellers-to-product-category.mdx +0 -40
  270. package/content/references/workflows/product/link-sellers-to-product.mdx +0 -40
  271. package/content/references/workflows/product/reject-product.mdx +0 -48
  272. package/content/references/workflows/product/request-product-change.mdx +0 -48
  273. package/content/references/workflows/product-attribute/add-product-attributes-to-product.mdx +0 -49
  274. package/content/references/workflows/product-attribute/create-and-link-product-attributes-to-product.mdx +0 -46
  275. package/content/references/workflows/product-attribute/create-product-attribute-values.mdx +0 -43
  276. package/content/references/workflows/product-attribute/create-product-attributes.mdx +0 -56
  277. package/content/references/workflows/product-attribute/delete-product-attribute-values.mdx +0 -30
  278. package/content/references/workflows/product-attribute/delete-product-attributes.mdx +0 -30
  279. package/content/references/workflows/product-attribute/remove-product-attributes-from-product.mdx +0 -29
  280. package/content/references/workflows/product-attribute/update-product-attribute-values.mdx +0 -44
  281. package/content/references/workflows/product-attribute/update-product-attributes-on-product.mdx +0 -41
  282. package/content/references/workflows/product-attribute/update-product-attributes.mdx +0 -48
  283. package/content/references/workflows/product-attribute/upsert-product-attribute-values.mdx +0 -43
  284. package/content/references/workflows/product-edit/auto-confirm-product-change.mdx +0 -39
  285. package/content/references/workflows/product-edit/cancel-product-change.mdx +0 -49
  286. package/content/references/workflows/product-edit/confirm-product-change.mdx +0 -57
  287. package/content/references/workflows/product-edit/create-product-change.mdx +0 -72
  288. package/content/references/workflows/product-edit/reject-product-change.mdx +0 -54
  289. package/content/references/workflows/product-edit/stage-product-change.mdx +0 -75
  290. package/content/references/workflows/seller/approve-seller.mdx +0 -36
  291. package/content/references/workflows/seller/create-seller-account.mdx +0 -59
  292. package/content/references/workflows/seller/create-seller-defaults.mdx +0 -22
  293. package/content/references/workflows/seller/create-sellers.mdx +0 -65
  294. package/content/references/workflows/seller/delete-seller-professional-details.mdx +0 -37
  295. package/content/references/workflows/seller/delete-sellers.mdx +0 -24
  296. package/content/references/workflows/seller/invite-seller.mdx +0 -28
  297. package/content/references/workflows/seller/suspend-seller.mdx +0 -37
  298. package/content/references/workflows/seller/terminate-seller.mdx +0 -37
  299. package/content/references/workflows/seller/unsuspend-seller.mdx +0 -36
  300. package/content/references/workflows/seller/unterminate-seller.mdx +0 -36
  301. package/content/references/workflows/seller/update-seller-address.mdx +0 -55
  302. package/content/references/workflows/seller/update-seller-payment-details.mdx +0 -52
  303. package/content/references/workflows/seller/update-seller-professional-details.mdx +0 -48
  304. package/content/references/workflows/seller/update-sellers.mdx +0 -57
  305. package/content/resources/ai/llms.mdx +0 -74
  306. package/content/resources/integrations/notifications.mdx +0 -39
  307. package/content/resources/integrations/search.mdx +0 -122
  308. package/content/resources/tutorials/configure-commissions.mdx +0 -127
  309. package/content/resources/tutorials/first-marketplace.mdx +0 -45
  310. package/content/resources/tutorials/handle-product-requests.mdx +0 -80
  311. package/content/resources/tutorials/import-export-products.mdx +0 -96
  312. package/content/resources/tutorials/seller-payouts-stripe.mdx +0 -89
  313. package/content/resources/tutorials/store-setup-checklist.mdx +0 -214
  314. package/content/tools/api-client.mdx +0 -155
  315. package/content/tools/cli.mdx +0 -196
  316. package/content/tools/dashboard-sdk.mdx +0 -35
@@ -1,24 +1,25 @@
1
1
  ---
2
- title: "Frontend"
3
- description: "Build panel UI the right way — @medusajs/ui usage, custom-field extensions (forms, tables, read-only section fields), and new pages, with the correct imports."
2
+ title: "Frontend Patterns"
3
+ sidebarTitle: "Frontend Patterns"
4
+ description: "Build Admin and Vendor panel UI with the shared design system: @medusajs/ui, custom-field extensions, and new pages, with the correct imports."
4
5
  ---
5
6
 
6
- The Admin and Vendor panels share one design system. The three things you'll actually do — style with `@medusajs/ui`, extend built-in screens with **custom fields**, and add **new pages** all have an established shape and, importantly, a set of **correct imports**. This page is that short list. The full reference is the [UI architecture](/rc/references/panel-extension-api).
7
+ The Admin and Vendor panels share one design system. You do three things with it: style with `@medusajs/ui`, extend built-in screens with custom fields, and add new pages. Each has an established shape and a set of correct imports. This page is that short list. For the full reference, see the [panel extensions reference](/references/panel-extensions/overview).
7
8
 
8
- ## Use `@medusajs/ui` and only it
9
+ ## Use @medusajs/ui, and only it
9
10
 
10
- <Warning>
11
- Never introduce a second UI library, and never restyle Medusa UI components with custom CSS. Build on the primitives; don't work around them.
12
- </Warning>
13
-
14
- - **Components** come from `@medusajs/ui`, **icons** from `@medusajs/icons`, and colours/spacing/type from Medusa UI **tokens** (`text-ui-fg-*`, `bg-ui-bg-*`, `border-ui-border-*`) — never hex, `rgb()`, or `text-gray-500`.
11
+ Components come from `@medusajs/ui`, icons from `@medusajs/icons`, and colours, spacing, and type from Medusa UI tokens (`text-ui-fg-*`, `bg-ui-bg-*`, `border-ui-border-*`). Never use hex, `rgb()`, or `text-gray-500`.
15
12
 
16
13
  ```tsx
17
14
  import { Container, Heading, Text, Button, Badge, StatusBadge, toast } from "@medusajs/ui"
18
15
  import { PencilSquare, Trash, EllipsisHorizontal } from "@medusajs/icons"
19
16
  ```
20
17
 
21
- - A **section** is a `Container` with the standard shell — a divided card with a header row:
18
+ <Warning>
19
+ Never introduce a second UI library, and never restyle Medusa UI components with custom CSS. Build on the primitives. Do not work around them.
20
+ </Warning>
21
+
22
+ A section is a `Container` with the standard shell: a divided card with a header row.
22
23
 
23
24
  ```tsx
24
25
  <Container className="divide-y p-0">
@@ -34,24 +35,24 @@ import { PencilSquare, Trash, EllipsisHorizontal } from "@medusajs/icons"
34
35
 
35
36
  ## Extend built-in screens with custom fields
36
37
 
37
- The primary way to customise an existing entity's screens (product, order, customer) is a **custom-fields config** one file per model that contributes form fields, table columns, and read-only section fields. See [Custom fields](/rc/resources/best-practices/custom-fields) for the full backendfrontend loop; here's the frontend surface with the right imports.
38
+ The primary way to customise an existing entity's screens (product, order, customer) is a custom-fields config: one file per model that contributes form fields, table columns, and read-only section fields. See [Custom fields](/rc/resources/best-practices/custom-fields) for the full backend and frontend loop. This section covers the frontend surface with the right imports.
38
39
 
39
- <Note>
40
- The two imports you need — and where each lives:
40
+ You need two imports, and each lives in a different package.
41
41
 
42
- ```tsx
43
- import { defineCustomFieldsConfig } from "@mercurjs/dashboard-sdk" // the config helper
44
- import { createFormHelper } from "@mercurjs/dashboard-shared" // typed form fields (zod)
45
- ```
42
+ ```tsx
43
+ import { defineCustomFieldsConfig } from "@mercurjs/dashboard-sdk" // the config helper
44
+ import { createFormHelper } from "@mercurjs/dashboard-shared" // typed form fields (zod)
45
+ ```
46
46
 
47
- `defineCustomFieldsConfig` is build-time config (SDK, zod-free); `createFormHelper` is the runtime form surface (dashboard-shared). Don't cross them over.
47
+ <Note>
48
+ `defineCustomFieldsConfig` is build-time config (SDK, zod-free). `createFormHelper` is the runtime form surface (dashboard-shared). Do not cross them over.
48
49
  </Note>
49
50
 
50
51
  ### Add form fields (edit / create)
51
52
 
52
- Contribute inputs into a built-in form `zone`. Values submit under `additional_data`:
53
+ Contribute inputs into a built-in form `zone`. Values submit under `additional_data`.
53
54
 
54
- ```tsx title="apps/vendor/src/custom-fields/product.tsx"
55
+ ```tsx apps/vendor/src/custom-fields/product.tsx
55
56
  import { defineCustomFieldsConfig } from "@mercurjs/dashboard-sdk"
56
57
  import { createFormHelper } from "@mercurjs/dashboard-shared"
57
58
 
@@ -77,7 +78,7 @@ export default defineCustomFieldsConfig({
77
78
 
78
79
  ### Change the list table
79
80
 
80
- Add or override a column (and add bulk actions) on the model's built-in list:
81
+ Add or override a column, and add bulk actions, on the model's built-in list.
81
82
 
82
83
  ```tsx
83
84
  list: {
@@ -87,11 +88,11 @@ list: {
87
88
  },
88
89
  ```
89
90
 
90
- ### Read-only fields in detail sections — e.g. surface (or change) a status
91
+ ### Read-only fields in detail sections
91
92
 
92
- `displays` add read-only rows into an existing detail-page section, keyed by `id` (unknown id **adds**, built-in id **replaces**, `component: null` **hides**). A read-only field can render a `StatusBadge`, and a section `action` can trigger a status change through a mutation:
93
+ Use `displays` to add read-only rows into an existing detail-page section, keyed by `id`. An unknown id adds a row, a built-in id replaces one, and `component: null` hides one. A read-only field can render a `StatusBadge`, and a section `action` can trigger a status change through a mutation.
93
94
 
94
- ```tsx title="apps/vendor/src/custom-fields/product.tsx"
95
+ ```tsx apps/vendor/src/custom-fields/product.tsx
95
96
  import { StatusBadge, Button, toast } from "@medusajs/ui"
96
97
 
97
98
  // inside defineCustomFieldsConfig(...)
@@ -128,14 +129,14 @@ displays: [
128
129
  ```
129
130
 
130
131
  <Tip>
131
- Read-only displays are the idiomatic way to expose (and act on) an entity's state an approval flag, a moderation status, an internal tag without rebuilding the detail page. The mutation still goes through the typed SDK and rides `additional_data` into a [workflow hook](/rc/resources/best-practices/custom-fields#the-full-override-flow-additional_data--route--workflow-hook), never a direct write.
132
+ Read-only displays are the idiomatic way to expose an entity's state, such as an approval flag, a moderation status, or an internal tag, and to act on it without rebuilding the detail page. The mutation still goes through the typed SDK and rides `additional_data` into a [workflow hook](/rc/resources/best-practices/custom-fields#the-full-override-flow-additional_data--route--workflow-hook), never a direct write.
132
133
  </Tip>
133
134
 
134
135
  ## Add a new page
135
136
 
136
- A brand-new screen is one file: drop a `page.tsx` under the host app's `src/routes/`. The SDK registers the route from the file path and builds the sidebar entry from an exported `config`.
137
+ A brand-new screen is one file. Drop a `page.tsx` under the host app's `src/routes/`. The SDK registers the route from the file path and builds the sidebar entry from an exported `config`.
137
138
 
138
- ```tsx title="apps/vendor/src/routes/reviews/page.tsx"
139
+ ```tsx apps/vendor/src/routes/reviews/page.tsx
139
140
  import { Container, Heading } from "@medusajs/ui"
140
141
  import { Star } from "@medusajs/icons"
141
142
  import type { RouteConfig } from "@mercurjs/dashboard-sdk"
@@ -157,12 +158,12 @@ export default function ReviewsPage() {
157
158
  ```
158
159
 
159
160
  <Note>
160
- Correct imports for a page: UI from `@medusajs/ui`, icons from `@medusajs/icons`, and the `RouteConfig` **type** from `@mercurjs/dashboard-sdk`. Dynamic segments use brackets `src/routes/reviews/[id]/page.tsx` `/reviews/:id`. See [Extending panels](/rc/resources/customization/extending-panels#routing-conventions).
161
+ Correct imports for a page: UI from `@medusajs/ui`, icons from `@medusajs/icons`, and the `RouteConfig` type from `@mercurjs/dashboard-sdk`. Dynamic segments use brackets: `src/routes/reviews/[id]/page.tsx` maps to `/reviews/:id`. See [Extending panels](/rc/resources/customization/extending-panels#routing-conventions).
161
162
  </Note>
162
163
 
163
164
  ## Compose a full page: layout, table, sections, edit
164
165
 
165
- For a real screen you assemble the same primitives the built-in pages use all re-exported from `@mercurjs/dashboard-shared`, so you import from **one** place instead of Medusa internals.
166
+ For a real screen you assemble the same primitives the built-in pages use. They are all re-exported from `@mercurjs/dashboard-shared`, so you import from one place instead of Medusa internals.
166
167
 
167
168
  ```tsx
168
169
  import {
@@ -179,11 +180,15 @@ import { Container, Heading, Text, Button, Input, toast } from "@medusajs/ui"
179
180
  import { createColumnHelper } from "@tanstack/react-table"
180
181
  ```
181
182
 
182
- ### Layout + list table
183
+ <Warning>
184
+ Import these primitives from `@mercurjs/dashboard-shared`, not from deep Medusa dashboard paths like `../../../components/table/data-table`. The shared package is the public, stable surface. Relative Medusa-internal imports are not available to consumer apps and break on upgrade.
185
+ </Warning>
186
+
187
+ ### Layout and list table
183
188
 
184
- Pick a layout `SingleColumnPage` for lists/simple pages, `TwoColumnPage` for a detail with a sidebar and mount a `DataTable` inside the standard section shell. Build columns with `createColumnHelper`, wire the table with `useDataTable`, page size 20, and `keepPreviousData` for smooth pagination:
189
+ Pick a layout: `SingleColumnPage` for lists and simple pages, `TwoColumnPage` for a detail with a sidebar. Mount a `DataTable` inside the standard section shell. Build columns with `createColumnHelper`, wire the table with `useDataTable`, use page size 20, and pass `keepPreviousData` for smooth pagination.
185
190
 
186
- ```tsx title="apps/vendor/src/routes/reviews/page.tsx"
191
+ ```tsx apps/vendor/src/routes/reviews/page.tsx
187
192
  const columnHelper = createColumnHelper<Review>()
188
193
 
189
194
  const columns = [
@@ -216,7 +221,7 @@ export default function ReviewsPage() {
216
221
 
217
222
  ### General section (label / value rows)
218
223
 
219
- On a detail page, a "general" section is a `Container` header row plus `SectionRow` label/value pairs the canonical way Medusa renders read-only entity data:
224
+ On a detail page, a "general" section is a `Container` header row plus `SectionRow` label and value pairs. This is the canonical way Medusa renders read-only entity data.
220
225
 
221
226
  ```tsx
222
227
  <Container className="divide-y p-0">
@@ -229,13 +234,13 @@ On a detail page, a "general" section is a `Container` header row plus `SectionR
229
234
  </Container>
230
235
  ```
231
236
 
232
- For a detail page with a sidebar, wrap sections in `TwoColumnPage` and place them under `TwoColumnPage.Main` / `TwoColumnPage.Sidebar` (each stacked with `gap-y-3`).
237
+ For a detail page with a sidebar, wrap sections in `TwoColumnPage` and place them under `TwoColumnPage.Main` and `TwoColumnPage.Sidebar`, each stacked with `gap-y-3`.
233
238
 
234
239
  ### Edit page (drawer)
235
240
 
236
- Quick edits live in a routed `RouteDrawer` with `Form` (React Hook Form + Zod). Gate the form until the entity has loaded, and use `useRouteModal().handleSuccess()` to close on save:
241
+ Quick edits live in a routed `RouteDrawer` with `Form` (React Hook Form plus Zod). Gate the form until the entity has loaded, and use `useRouteModal().handleSuccess()` to close on save.
237
242
 
238
- ```tsx title="apps/vendor/src/routes/reviews/[id]/edit/page.tsx"
243
+ ```tsx apps/vendor/src/routes/reviews/[id]/edit/page.tsx
239
244
  export default function EditReviewPage() {
240
245
  return (
241
246
  <RouteDrawer>
@@ -244,23 +249,19 @@ export default function EditReviewPage() {
244
249
  <Heading>Edit review</Heading>
245
250
  </RouteDrawer.Title>
246
251
  </RouteDrawer.Header>
247
- {/* <EditReviewForm /> RouteDrawer.Form + KeyboundForm, gated on !isPending && !!review */}
252
+ {/* <EditReviewForm />, RouteDrawer.Form + KeyboundForm, gated on !isPending && !!review */}
248
253
  </RouteDrawer>
249
254
  )
250
255
  }
251
256
  ```
252
257
 
253
- <Warning>
254
- Import these primitives from `@mercurjs/dashboard-shared`, **not** from deep Medusa dashboard paths like `../../../components/table/data-table`. The shared package is the public, stable surface; relative Medusa-internal imports are not available to consumer apps and break on upgrade.
255
- </Warning>
256
-
257
258
  ## Data only through the typed SDK
258
259
 
259
260
  <Warning>
260
- Never call `fetch` directly from a page. All HTTP goes through the typed SDK `sdk.admin.*` in the admin panel, `sdk.vendor.*` in the vendor panel wrapped in TanStack Query hooks.
261
+ Never call `fetch` directly from a page. All HTTP goes through the typed SDK (`sdk.admin.*` in the admin panel, `sdk.vendor.*` in the vendor panel), wrapped in TanStack Query hooks.
261
262
  </Warning>
262
263
 
263
- ```ts title="src/hooks/api/reviews.tsx"
264
+ ```ts src/hooks/api/reviews.tsx
264
265
  import { useQuery } from "@tanstack/react-query"
265
266
  import { sdk } from "../../lib/client"
266
267
  import { queryKeysFactory } from "@mercurjs/dashboard-shared"
@@ -274,13 +275,24 @@ export const useReviews = (query?: Record<string, unknown>) =>
274
275
  })
275
276
  ```
276
277
 
277
- Invalidate `lists()` / `details()` / `detail(id)` in mutations, throw on `isError` so the route `ErrorBoundary` catches it, and show a `Skeleton` while loading.
278
+ Invalidate `lists()`, `details()`, and `detail(id)` in mutations. Throw on `isError` so the route `ErrorBoundary` catches it. Show a `Skeleton` while loading.
278
279
 
279
280
  ## Checklist for panel work
280
281
 
281
- - Built only from `@medusajs/ui` + `@medusajs/icons`; Medusa UI tokens only, no custom CSS.
282
- - Extending an existing screen a `defineCustomFieldsConfig` file (`@mercurjs/dashboard-sdk`) with `createFormHelper` (`@mercurjs/dashboard-shared`); forms submit under `additional_data`.
283
- - Read-only state (status/flags) surfaced via `displays`; changes go through the typed SDK + a workflow hook, not a direct write.
284
- - New screen a `page.tsx` under `src/routes/` with a typed `RouteConfig`; compose it from `SingleColumnPage`/`TwoColumnPage`, `DataTable`, `SectionRow`, and `RouteDrawer` all imported from `@mercurjs/dashboard-shared`, never Medusa-internal paths.
285
- - Data via `sdk.admin.*` / `sdk.vendor.*` in TanStack Query hooks; no raw `fetch`; mutations invalidate the right keys.
286
- - Every visible string translated; every interactive element has a `data-testid`.
282
+ - **UI primitives:** built only from `@medusajs/ui` and `@medusajs/icons`, Medusa UI tokens only, no custom CSS.
283
+ - **Extending a screen:** a `defineCustomFieldsConfig` file (`@mercurjs/dashboard-sdk`) with `createFormHelper` (`@mercurjs/dashboard-shared`). Forms submit under `additional_data`.
284
+ - **Read-only state:** status and flags surfaced via `displays`. Changes go through the typed SDK and a workflow hook, not a direct write.
285
+ - **New screen:** a `page.tsx` under `src/routes/` with a typed `RouteConfig`. Compose it from `SingleColumnPage` or `TwoColumnPage`, `DataTable`, `SectionRow`, and `RouteDrawer`, all imported from `@mercurjs/dashboard-shared`, never Medusa-internal paths.
286
+ - **Data:** via `sdk.admin.*` or `sdk.vendor.*` in TanStack Query hooks. No raw `fetch`. Mutations invalidate the right keys.
287
+ - **Strings and test ids:** every visible string translated, every interactive element has a `data-testid`.
288
+
289
+ ## Next steps
290
+
291
+ <CardGroup cols={2}>
292
+ <Card title="Panel extensions reference" href="/references/panel-extensions/overview">
293
+ The full reference for custom fields, widgets, and new pages.
294
+ </Card>
295
+ <Card title="Custom fields" href="/rc/resources/best-practices/custom-fields">
296
+ The full backend and frontend loop, including the workflow hook.
297
+ </Card>
298
+ </CardGroup>
@@ -1,19 +1,22 @@
1
1
  ---
2
- title: "Module links"
3
- description: "Relate modules without coupling them — defineLink, the link-direction rule, built-in link steps, and filtering by links."
2
+ title: "How to Link Two Modules"
3
+ sidebarTitle: "Link Modules"
4
+ description: "Relate modules without coupling them using defineLink, the link-direction rule, built-in link steps, and cross-link filtering."
4
5
  ---
5
6
 
6
- Modules are isolated: a module never imports another module's service or points a foreign key at another module's table (see [Modules](/rc/resources/best-practices/modules)). Relationships between modules are declared **outside** the modules, as **links**, and read through **Query**. This is what keeps each module independently migratable and upgrade-safe.
7
+ Modules stay isolated, so you relate them with links declared outside the modules and read through Query.
8
+
9
+ A module never imports another module's service, and it never points a foreign key at another module's table (see [Modules](/rc/resources/best-practices/modules)). You declare relationships between modules **outside** the modules, as **links**, and read them through **Query**. This is what keeps each module independently migratable and upgrade-safe.
7
10
 
8
11
  <Note>
9
- Links are a [Medusa framework primitive](https://docs.medusajs.com/learn/fundamentals/module-links). The examples below link a custom **Brand** module to Medusa's built-in **Product** module the kind of relationship you'd add in your own project.
12
+ Links are a [Medusa framework primitive](https://docs.medusajs.com/learn/fundamentals/module-links). The examples below link a custom **Brand** module to Medusa's built-in **Product** module. It is the kind of relationship you would add in your own project.
10
13
  </Note>
11
14
 
12
- ## `defineLink`
15
+ ## Define a link with `defineLink`
13
16
 
14
- A link is a small file that associates two linkable data models. Define it once and sync it to the database with a migration.
17
+ A link is a small file that associates two linkable data models. You define it once and sync it to the database with a migration.
15
18
 
16
- ```ts title="src/links/product-brand.ts"
19
+ ```ts src/links/product-brand.ts
17
20
  import { defineLink } from "@medusajs/framework/utils"
18
21
  import ProductModule from "@medusajs/medusa/product"
19
22
  import BrandModule from "../modules/brand"
@@ -26,13 +29,13 @@ export default defineLink(
26
29
 
27
30
  After adding or changing a link, generate and run the migration so the link table exists:
28
31
 
29
- ```bash
32
+ ```bash Terminal
30
33
  npx medusa db:migrate
31
34
  ```
32
35
 
33
- Once linked, you read across the boundary with Query never by calling the other module's service:
36
+ Once linked, you read across the boundary with Query, never by calling the other module's service:
34
37
 
35
- ```ts
38
+ ```ts Read across the link with Query
36
39
  const { data: products } = await query.graph({
37
40
  entity: "product",
38
41
  fields: ["id", "title", "brand.*"], // follows the product ↔ brand link
@@ -41,14 +44,14 @@ const { data: products } = await query.graph({
41
44
 
42
45
  ## The link-direction rule
43
46
 
44
- The **order of arguments to `defineLink` is meaningful** and cardinality is controlled with `isList`. Read it left-to-right as "the left model links to the right model".
47
+ The order of arguments to `defineLink` is meaningful, and you control cardinality with `isList`. Read it left-to-right as "the left model links to the right model".
45
48
 
46
- - `defineLink(A.linkable.a, B.linkable.b)` one `a` one `b`.
47
- - Wrap a side in `{ linkable, isList: true }` to make it the "many" side.
49
+ - **`defineLink(A.linkable.a, B.linkable.b)`:** one `a` links to one `b`.
50
+ - **`isList: true`:** wrap a side in `{ linkable, isList: true }` to make it the "many" side.
48
51
 
49
52
  If one brand has many products but each product belongs to a single brand, mark the **product** side as the list:
50
53
 
51
- ```ts title="src/links/product-brand.ts — one brand, many products"
54
+ ```ts src/links/product-brand.ts
52
55
  import { defineLink } from "@medusajs/framework/utils"
53
56
  import ProductModule from "@medusajs/medusa/product"
54
57
  import BrandModule from "../modules/brand"
@@ -62,9 +65,9 @@ export default defineLink(
62
65
  )
63
66
  ```
64
67
 
65
- For a many-to-many relationship (a product can carry many brands *and* a brand spans many products) mark both sides as lists and pin an explicit table name:
68
+ For a many-to-many relationship, where a product can carry many brands and a brand spans many products, mark both sides as lists and pin an explicit table name:
66
69
 
67
- ```ts title="src/links/product-brand.ts — many-to-many"
70
+ ```ts src/links/product-brand.ts
68
71
  export default defineLink(
69
72
  { linkable: ProductModule.linkable.product, isList: true },
70
73
  { linkable: BrandModule.linkable.brand, isList: true },
@@ -77,19 +80,19 @@ export default defineLink(
77
80
  ```
78
81
 
79
82
  <Warning>
80
- Direction determines the generated relation names and the shape of the link table. Getting it backwards produces a link that "works" but exposes the wrong nesting (`brand.products` vs `product.brands`) and is painful to migrate away from. Decide the natural reading direction first, then set `isList` on the many side(s).
83
+ Direction determines the generated relation names and the shape of the link table. Getting it backwards produces a link that "works" but exposes the wrong nesting (`brand.products` vs `product.brands`), and it is painful to migrate away from. Decide the natural reading direction first, then set `isList` on the many side or sides.
81
84
  </Warning>
82
85
 
83
- ## Built-in link steps — create links inside workflows
86
+ ## Create links inside workflows
84
87
 
85
- Links are **data**, so creating or removing one is a mutation and must happen inside a [workflow](/rc/resources/best-practices/workflows) through the built-in link steps never by writing to the link table directly.
88
+ Links are **data**, so creating or removing one is a mutation. It must happen inside a [workflow](/rc/resources/best-practices/workflows) through the built-in link steps. Never write to the link table directly.
86
89
 
87
- - `createRemoteLinkStep` create links (and it compensates by removing them on failure).
88
- - `dismissRemoteLinkStep` remove links.
90
+ - **`createRemoteLinkStep`:** creates links, and compensates by removing them on failure.
91
+ - **`dismissRemoteLinkStep`:** removes links.
89
92
 
90
93
  Build the link definitions with `transform` (never inline logic in the composition function), then pass them to the step. Each entry names the two modules and the ids to associate:
91
94
 
92
- ```ts title="Linking a product to a brand inside a workflow"
95
+ ```ts Linking a product to a brand inside a workflow
93
96
  import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
94
97
  import { Modules } from "@medusajs/framework/utils"
95
98
  import { LinkDefinition } from "@medusajs/framework/types"
@@ -116,42 +119,53 @@ createRemoteLinkStep(productBrandLinks)
116
119
 
117
120
  This is the distinction that trips people up:
118
121
 
119
- - **Reading** linked data fetching `brand.*` alongside a product works with `query.graph`. Query aggregates the two modules' data to build the result.
120
- - **Filtering** by a linked module's field "give me products *where* `brand.id = X`" does **not** work with `query.graph`.
122
+ - **Reading** linked data (fetching `brand.*` alongside a product) works with `query.graph`. Query aggregates the two modules' data to build the result.
123
+ - **Filtering** by a linked module's field ("give me products *where* `brand.id = X`") does **not** work with `query.graph`.
121
124
 
122
125
  <Warning>
123
- `query.graph` **cannot filter by a linked (cross-module) field.** Because modules are isolated and Query aggregates their data after the fact, there's no join to filter on. Passing `filters: { brand: { id } }` to `query.graph` will not scope products by brand.
126
+ `query.graph` cannot filter by a linked (cross-module) field. Because modules are isolated and Query aggregates their data after the fact, there is no join to filter on. Passing `filters: { brand: { id } }` to `query.graph` will not scope products by brand.
124
127
  </Warning>
125
128
 
126
- You can still filter by a field that lives on the entity's **own** module (a plain column like `product.status` or `offer.seller_id`) that's a normal `query.graph` filter. It's only *linked-module* fields that need a different tool.
129
+ You can still filter by a field that lives on the entity's **own** module, a plain column such as `product.status` or `offer.seller_id`. That is a normal `query.graph` filter. Only *linked-module* fields need a different tool.
127
130
 
128
- ### Filtering by a linked field the Index Module
131
+ ### Filter by a linked field with the Index Module
129
132
 
130
133
  Cross-module filtering is what the [Index Module](https://docs.medusajs.com/learn/fundamentals/module-links/index-module) (`@medusajs/index`) exists for. It ingests data models into a single relational store on startup, so you can filter one entity by another's fields. Install it, make sure both models are ingested, and query with `query.index` instead of `query.graph`:
131
134
 
132
- ```ts title="Filter products by their linked brand query.index, not query.graph"
135
+ ```ts Filter products by their linked brand with query.index
133
136
  const { data: products, metadata } = await query.index({
134
137
  entity: "product",
135
138
  fields: ["id", "title", "brand.name"],
136
139
  filters: {
137
140
  brand: {
138
- id: brandId, // ✅ cross-module filter resolved by the Index Module
141
+ id: brandId, // ✅ cross-module filter, resolved by the Index Module
139
142
  },
140
143
  },
141
144
  })
142
145
  ```
143
146
 
144
147
  <Note>
145
- By default Medusa ingests only `Product`, `ProductVariant`, `Price`, `PriceSet`, and `SalesChannel`. To filter products by a **custom** module like Brand, you must [ingest that model](https://docs.medusajs.com/learn/fundamentals/module-links/index-module#how-to-ingest-custom-data-models) into the Index Module first. The Index Module is still marked experimental, though it powers filtering in the Medusa Admin.
148
+ By default Medusa ingests only `Product`, `ProductVariant`, `Price`, `PriceSet`, and `SalesChannel`. To filter products by a **custom** module such as Brand, you must [ingest that model](https://docs.medusajs.com/learn/fundamentals/module-links/index-module#how-to-ingest-custom-data-models) into the Index Module first. The Index Module is still marked experimental, though it powers filtering in the Medusa Admin.
146
149
  </Note>
147
150
 
148
- `query.index` takes the same shape as `query.graph` (entity, fields, filters, pagination), so a route handler can forward `req.filterableFields` to it exactly the same way the only change is `graph` `index`.
151
+ `query.index` takes the same shape as `query.graph` (entity, fields, filters, pagination), so a route handler can forward `req.filterableFields` to it exactly the same way. The only change is `graph` to `index`.
149
152
 
150
153
  ## Checklist for a link
151
154
 
152
155
  - Declared in its own file under `src/links/`, using `defineLink`.
153
- - Argument order reflects the natural reading direction; `isList` set on the many side(s).
156
+ - Argument order reflects the natural reading direction, with `isList` set on the many side or sides.
154
157
  - Migration generated and run (`medusa db:migrate`).
155
158
  - Cross-module **reads** go through `query.graph`, never a service-to-service call.
156
- - Cross-module **filters** go through `query.index` (Index Module, with the model ingested) `query.graph` can't filter by a linked field.
157
- - Links are created/removed only inside workflows via `createRemoteLinkStep` / `dismissRemoteLinkStep`.
159
+ - Cross-module **filters** go through `query.index` (Index Module, with the model ingested). `query.graph` cannot filter by a linked field.
160
+ - Links are created and removed only inside workflows via `createRemoteLinkStep` and `dismissRemoteLinkStep`.
161
+
162
+ ## Next steps
163
+
164
+ <CardGroup cols={2}>
165
+ <Card title="Modules" href="/rc/resources/best-practices/modules">
166
+ Keep modules isolated so links stay the only boundary between them.
167
+ </Card>
168
+ <Card title="Workflows" href="/rc/resources/best-practices/workflows">
169
+ Create and remove links inside compensating workflow steps.
170
+ </Card>
171
+ </CardGroup>
@@ -1,19 +1,24 @@
1
1
  ---
2
- title: "Modules"
3
- description: "Keep modules thin — data access and CRUD only. Naming, decorators, and what must never live in a module service."
2
+ title: "How to Create a Custom Module"
3
+ sidebarTitle: "Create a Module"
4
+ description: "Keep a module thin: it owns one domain's data and its CRUD, with no orchestration, events, or cross-module calls."
4
5
  ---
5
6
 
6
- A module is the lowest layer of the [architecture](/rc/resources/best-practices/overview): it owns exactly one domain's data and nothing else. Modules are isolated — they never reach into another module, never orchestrate a business operation, and never react to events. All of that lives one layer up, in [workflows](/rc/resources/best-practices/workflows).
7
+ A module is the lowest layer of the [architecture](/rc/resources/best-practices/overview). It owns exactly one domain's data and nothing else.
8
+
9
+ Modules are isolated. They never reach into another module, never orchestrate a business operation, and never react to events. All of that lives one layer up, in [workflows](/rc/resources/best-practices/workflows).
7
10
 
8
11
  <Note>
9
- A Mercur module is a standard [Medusa module](https://docs.medusajs.com/learn/fundamentals/modules). The examples below build a small **Brand** module the kind of custom module you'd add to your own project alongside the built-in ones so the rules stand on their own rather than relying on Mercur internals.
12
+ A Mercur module is a standard [Medusa module](https://docs.medusajs.com/learn/fundamentals/modules). The examples below build a small **Brand** module. It is the kind of custom module you add to your own project alongside the built-in ones, so the rules stand on their own rather than relying on Mercur internals.
10
13
  </Note>
11
14
 
12
15
  ## Thin CRUD only
13
16
 
14
- A module service exists to read and write its own tables. Extend `MedusaService({ ...models })` and you get typed `list`, `listAndCount`, `retrieve`, `create`, `update`, and `delete` methods for every model for free use them.
17
+ A module service exists to read and write its own tables. Extend `MedusaService({ ...models })` and you get typed `list`, `listAndCount`, `retrieve`, `create`, `update`, and `delete` methods for every model for free. Use them.
18
+
19
+ Define the model with plain columns.
15
20
 
16
- ```ts title="src/modules/brand/models/brand.ts"
21
+ ```ts src/modules/brand/models/brand.ts
17
22
  import { model } from "@medusajs/framework/utils"
18
23
 
19
24
  export const Brand = model.define("brand", {
@@ -22,7 +27,9 @@ export const Brand = model.define("brand", {
22
27
  })
23
28
  ```
24
29
 
25
- ```ts title="src/modules/brand/service.ts"
30
+ Extend `MedusaService` to get the generated CRUD methods.
31
+
32
+ ```ts src/modules/brand/service.ts
26
33
  import { MedusaService } from "@medusajs/framework/utils"
27
34
  import { Brand } from "./models/brand"
28
35
 
@@ -36,9 +43,9 @@ class BrandModuleService extends MedusaService({
36
43
  export default BrandModuleService
37
44
  ```
38
45
 
39
- Only add a custom method when the logic is **about this module's own data** and can't be expressed with the generated methods for example, a specialised query. When you do, use Medusa's DI decorators so the method runs in the ambient context:
46
+ Add a custom method only when the logic is about this module's own data and can't be expressed with the generated methods, such as a specialised query. When you do, use Medusa's DI decorators so the method runs in the ambient context.
40
47
 
41
- ```ts title="A justified custom method — still single-module"
48
+ ```ts src/modules/brand/service.ts
42
49
  class BrandModuleService extends MedusaService({ Brand }) {
43
50
  @InjectManager()
44
51
  async listActiveBrands(
@@ -51,14 +58,16 @@ class BrandModuleService extends MedusaService({ Brand }) {
51
58
  ```
52
59
 
53
60
  <Warning>
54
- **What must never live in a module service:** business orchestration, calls to another module's service, event emission, HTTP concerns, or anything that mutates data outside this module. If a method needs a second module's data or writes across a boundary, it belongs in a [workflow](/rc/resources/best-practices/workflows), not here. See the [logic-placement cheat sheet](/rc/resources/best-practices/overview#logic-placement-cheat-sheet).
61
+ Some logic must never live in a module service: business orchestration, calls to another module's service, event emission, HTTP concerns, or anything that mutates data outside this module. If a method needs a second module's data or writes across a boundary, it belongs in a [workflow](/rc/resources/best-practices/workflows), not here. See the [logic-placement cheat sheet](/rc/resources/best-practices/overview#logic-placement-cheat-sheet).
55
62
  </Warning>
56
63
 
57
64
  ## Naming
58
65
 
66
+ Follow four conventions so the module reads like the built-in ones.
67
+
59
68
  - **Register the module by a stable id constant.** Export the module id and register the service against it:
60
69
 
61
- ```ts title="src/modules/brand/index.ts"
70
+ ```ts src/modules/brand/index.ts
62
71
  import { Module } from "@medusajs/framework/utils"
63
72
  import BrandModuleService from "./service"
64
73
 
@@ -70,14 +79,14 @@ class BrandModuleService extends MedusaService({ Brand }) {
70
79
  ```
71
80
 
72
81
  <Tip>
73
- Mercur's own modules follow the same pattern but read their id from the shared `MercurModules` enum in `@mercurjs/types` (e.g. `Module(MercurModules.SELLER, …)`). For a project-local module, a single exported constant like `BRAND_MODULE` is enough just never inline the raw string in more than one place.
82
+ Mercur's own modules follow the same pattern but read their id from the shared `MercurModules` enum in `@mercurjs/types` (e.g. `Module(MercurModules.SELLER, …)`). For a project-local module, a single exported constant such as `BRAND_MODULE` is enough. Just never inline the raw string in more than one place.
74
83
  </Tip>
75
84
 
76
- - **Methods are `camelCase` and model-suffixed.** Medusa generates `listBrands`, `createBrands`, `retrieveBrand` match that casing and pluralisation when you add or override methods. Private helpers end with a trailing underscore (`computeBrandStats_`).
77
- - **Models are lowercase-defined, referenced by their key.** `model.define("brand", { ... })`; the object key you pass to `MedusaService` (`Brand`) is what drives the generated method names.
78
- - **Types live next to the module (or in a shared types package).** Export DTOs like `BrandDTO` and import them; never redeclare a model's shape ad hoc. See [Types & augmentation](/rc/resources/best-practices/types).
85
+ - **Methods are `camelCase` and model-suffixed.** Medusa generates `listBrands`, `createBrands`, and `retrieveBrand`. Match that casing and pluralisation when you add or override methods. Private helpers end with a trailing underscore (`computeBrandStats_`).
86
+ - **Models are lowercase-defined, referenced by their key.** `model.define("brand", { ... })`. The object key you pass to `MedusaService` (`Brand`) is what drives the generated method names.
87
+ - **Types live next to the module, or in a shared types package.** Export DTOs such as `BrandDTO` and import them. Never redeclare a model's shape ad hoc. See [Types & augmentation](/rc/resources/best-practices/types).
79
88
 
80
- ## Do not call `.linkable()` links are declared separately
89
+ ## Do not call `.linkable()`: links are declared separately
81
90
 
82
91
  It is tempting to relate two modules by pointing a model at another module's table. Don't. A module model must not reference another module's data, and you should not wire relationships inside the model definition.
83
92
 
@@ -85,9 +94,9 @@ It is tempting to relate two modules by pointing a model at another module's tab
85
94
  Cross-module relationships are declared **outside** the modules, with `defineLink`, and read through **Query**. A module never imports another module's `.linkable` shape to build a foreign key into it. Keeping models link-free is what lets modules stay independently migratable and upgrade-safe.
86
95
  </Warning>
87
96
 
88
- Define the relationship as its own link file instead covered in full on [Module links](/rc/resources/best-practices/module-links):
97
+ Define the relationship as its own link file instead. This is covered in full on [Module links](/rc/resources/best-practices/module-links).
89
98
 
90
- ```ts title="Relationship declared as a link, not inside the model"
99
+ ```ts Relationship declared as a link, not inside the model
91
100
  import { defineLink } from "@medusajs/framework/utils"
92
101
  import ProductModule from "@medusajs/medusa/product"
93
102
  import BrandModule from "../modules/brand"
@@ -98,9 +107,9 @@ export default defineLink(
98
107
  )
99
108
  ```
100
109
 
101
- The model itself stays flat plain columns, no relations pointing at other modules:
110
+ The model itself stays flat: plain columns, no relations pointing at other modules.
102
111
 
103
- ```ts title="src/modules/brand/models/brand.ts"
112
+ ```ts src/modules/brand/models/brand.ts
104
113
  export const Brand = model.define("brand", {
105
114
  id: model.id().primaryKey(),
106
115
  name: model.text(),
@@ -111,7 +120,7 @@ export const Brand = model.define("brand", {
111
120
 
112
121
  ## Decorators
113
122
 
114
- Custom service methods that touch the database use Medusa's dependency-injection decorators so they participate in the ambient transaction and shared context:
123
+ Custom service methods that touch the database use Medusa's dependency-injection decorators so they participate in the ambient transaction and shared context.
115
124
 
116
125
  | Decorator | Use it on | Purpose |
117
126
  | --- | --- | --- |
@@ -119,7 +128,7 @@ Custom service methods that touch the database use Medusa's dependency-injection
119
128
  | `@InjectTransactionManager()` | Write methods | Runs the method inside a transaction, enabling rollback. |
120
129
  | `@MedusaContext()` | The trailing `sharedContext` parameter | Threads the request/transaction context through the call. |
121
130
 
122
- ```ts title="Decorator pattern for a custom write"
131
+ ```ts Decorator pattern for a custom write
123
132
  @InjectTransactionManager()
124
133
  async deactivateBrand(
125
134
  id: string,
@@ -130,15 +139,32 @@ async deactivateBrand(
130
139
  ```
131
140
 
132
141
  <Tip>
133
- If you don't need a custom method, don't write one. The generated `MedusaService` methods already carry the right decorators and transaction behaviour reaching for them first keeps modules thin by default.
142
+ If you don't need a custom method, don't write one. The generated `MedusaService` methods already carry the right decorators and transaction behaviour. Reaching for them first keeps modules thin by default.
134
143
  </Tip>
135
144
 
136
145
  ## Checklist for a module
137
146
 
138
- - Extends `MedusaService({ ...models })`; leans on generated CRUD.
147
+ - Extends `MedusaService({ ...models })` and leans on generated CRUD.
139
148
  - Registered with `Module(BRAND_MODULE, { service })` against a stable id.
140
149
  - No import of, or call into, any other module's service.
141
- - Models are flat no `.linkable()` wiring, no cross-module foreign keys.
142
- - Custom methods use `@InjectManager` / `@InjectTransactionManager` + `@MedusaContext`.
150
+ - Models are flat: no `.linkable()` wiring, no cross-module foreign keys.
151
+ - Custom methods use `@InjectManager` / `@InjectTransactionManager` plus `@MedusaContext`.
143
152
  - DTOs are exported and imported, never redeclared inline.
144
- - No orchestration, no events, no HTTP those live in workflows and routes.
153
+ - No orchestration, no events, no HTTP. Those live in workflows and routes.
154
+
155
+ ## Next steps
156
+
157
+ <CardGroup cols={2}>
158
+ <Card title="Workflows" href="/rc/resources/best-practices/workflows">
159
+ Orchestrate business operations across modules, with compensation on failure.
160
+ </Card>
161
+ <Card title="Module links" href="/rc/resources/best-practices/module-links">
162
+ Relate two modules with `defineLink` and read the relationship through Query.
163
+ </Card>
164
+ <Card title="Types & augmentation" href="/rc/resources/best-practices/types">
165
+ Export DTOs and share a model's shape instead of redeclaring it inline.
166
+ </Card>
167
+ <Card title="Best practices overview" href="/rc/resources/best-practices/overview">
168
+ See the layered architecture and the logic-placement cheat sheet.
169
+ </Card>
170
+ </CardGroup>