@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.
- package/content/home.mdx +107 -0
- package/content/learn/architecture.mdx +168 -83
- package/content/learn/introduction.mdx +161 -56
- package/content/learn/migration-to-2-0.mdx +187 -0
- package/content/learn/why-mercur.mdx +91 -0
- package/content/platform/attribute/concepts/attribute-types.mdx +59 -0
- package/content/platform/attribute/concepts/global-vs-inline.mdx +50 -0
- package/content/platform/attribute/concepts/variant-axes.mdx +61 -0
- package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +69 -0
- package/content/platform/attribute/guides/create-a-variant-axis.mdx +83 -0
- package/content/platform/attribute/guides/create-an-attribute.mdx +81 -0
- package/content/platform/attribute/overview.mdx +87 -0
- package/content/platform/attribute/reference/data-models.mdx +56 -0
- package/content/platform/attribute/reference/events.mdx +41 -0
- package/content/platform/attribute/reference/links.mdx +45 -0
- package/content/platform/attribute/reference/service.mdx +49 -0
- package/content/platform/attribute/reference/workflows.mdx +45 -0
- package/content/platform/catalog/concepts/master-products.mdx +54 -0
- package/content/platform/catalog/concepts/product-seller-allowlist.mdx +48 -0
- package/content/platform/catalog/concepts/status-lifecycle.mdx +58 -0
- package/content/platform/catalog/concepts/variants-categories-collections.mdx +49 -0
- package/content/platform/catalog/guides/allowlist-stores.mdx +59 -0
- package/content/platform/catalog/guides/create-a-master-product.mdx +70 -0
- package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +79 -0
- package/content/platform/catalog/overview.mdx +90 -0
- package/content/platform/catalog/reference/data-models.mdx +66 -0
- package/content/platform/catalog/reference/events.mdx +41 -0
- package/content/platform/catalog/reference/links.mdx +44 -0
- package/content/platform/catalog/reference/service.mdx +52 -0
- package/content/platform/catalog/reference/workflows.mdx +40 -0
- package/content/platform/commission/concepts/order-commission-lines.mdx +65 -0
- package/content/platform/commission/concepts/rule-matching.mdx +86 -0
- package/content/platform/commission/concepts/rules-and-rates.mdx +84 -0
- package/content/platform/commission/guides/batch-update-rules.mdx +58 -0
- package/content/platform/commission/guides/create-a-rate.mdx +72 -0
- package/content/platform/commission/guides/refresh-order-commission-lines.mdx +55 -0
- package/content/platform/commission/overview.mdx +86 -0
- package/content/platform/commission/reference/data-models.mdx +75 -0
- package/content/platform/commission/reference/events.mdx +58 -0
- package/content/platform/commission/reference/links.mdx +45 -0
- package/content/platform/commission/reference/service.mdx +51 -0
- package/content/platform/commission/reference/workflows.mdx +40 -0
- package/content/platform/offer/concepts/pricing-and-inventory.mdx +72 -0
- package/content/platform/offer/concepts/shipping.mdx +48 -0
- package/content/platform/offer/concepts/what-is-an-offer.mdx +65 -0
- package/content/platform/offer/guides/bulk-create-offers.mdx +84 -0
- package/content/platform/offer/guides/create-an-offer.mdx +83 -0
- package/content/platform/offer/guides/manage-offer-inventory.mdx +53 -0
- package/content/platform/offer/overview.mdx +84 -0
- package/content/platform/offer/reference/data-models.mdx +56 -0
- package/content/platform/offer/reference/events.mdx +39 -0
- package/content/platform/offer/reference/links.mdx +60 -0
- package/content/platform/offer/reference/service.mdx +53 -0
- package/content/platform/offer/reference/workflows.mdx +38 -0
- package/content/platform/order-group/concepts/computed-totals.mdx +59 -0
- package/content/platform/order-group/concepts/order-splitting.mdx +61 -0
- package/content/platform/order-group/concepts/the-order-group.mdx +64 -0
- package/content/platform/order-group/guides/list-order-groups.mdx +67 -0
- package/content/platform/order-group/guides/retrieve-an-order-group.mdx +63 -0
- package/content/platform/order-group/guides/split-a-cart.mdx +58 -0
- package/content/platform/order-group/overview.mdx +83 -0
- package/content/platform/order-group/reference/data-models.mdx +40 -0
- package/content/platform/order-group/reference/events.mdx +37 -0
- package/content/platform/order-group/reference/links.mdx +45 -0
- package/content/platform/order-group/reference/service.mdx +50 -0
- package/content/platform/order-group/reference/workflows.mdx +39 -0
- package/content/platform/payout/concepts/account-lifecycle.mdx +75 -0
- package/content/platform/payout/concepts/accounts-and-onboarding.mdx +70 -0
- package/content/platform/payout/concepts/payout-pipeline.mdx +99 -0
- package/content/platform/payout/guides/create-a-payout-account.mdx +59 -0
- package/content/platform/payout/guides/process-a-provider-webhook.mdx +74 -0
- package/content/platform/payout/guides/start-provider-onboarding.mdx +50 -0
- package/content/platform/payout/overview.mdx +86 -0
- package/content/platform/payout/reference/data-models.mdx +61 -0
- package/content/platform/payout/reference/events.mdx +48 -0
- package/content/platform/payout/reference/links.mdx +36 -0
- package/content/platform/payout/reference/service.mdx +53 -0
- package/content/platform/payout/reference/workflows.mdx +32 -0
- package/content/platform/product-edit/concepts/change-actions.mdx +66 -0
- package/content/platform/product-edit/concepts/change-pipeline.mdx +69 -0
- package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +66 -0
- package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +76 -0
- package/content/platform/product-edit/guides/edit-a-product.mdx +74 -0
- package/content/platform/product-edit/guides/request-a-revision.mdx +54 -0
- package/content/platform/product-edit/overview.mdx +85 -0
- package/content/platform/product-edit/reference/data-models.mdx +65 -0
- package/content/platform/product-edit/reference/events.mdx +47 -0
- package/content/platform/product-edit/reference/links.mdx +39 -0
- package/content/platform/product-edit/reference/service.mdx +43 -0
- package/content/platform/product-edit/reference/workflows.mdx +49 -0
- package/content/platform/review/concepts/product-vs-seller-reviews.mdx +63 -0
- package/content/platform/review/concepts/ratings-and-moderation.mdx +64 -0
- package/content/platform/review/concepts/the-review-model.mdx +56 -0
- package/content/platform/review/guides/compute-aggregate-ratings.mdx +57 -0
- package/content/platform/review/guides/create-a-review.mdx +55 -0
- package/content/platform/review/guides/moderate-a-review.mdx +58 -0
- package/content/platform/review/guides/respond-to-a-review.mdx +61 -0
- package/content/platform/review/overview.mdx +87 -0
- package/content/platform/review/reference/data-models.mdx +36 -0
- package/content/platform/review/reference/events.mdx +61 -0
- package/content/platform/review/reference/links.mdx +43 -0
- package/content/platform/review/reference/service.mdx +54 -0
- package/content/platform/review/reference/workflows.mdx +31 -0
- package/content/platform/store/concepts/lifecycle.mdx +62 -0
- package/content/platform/store/concepts/store-entity.mdx +53 -0
- package/content/platform/store/concepts/team.mdx +50 -0
- package/content/platform/store/guides/create-a-store.mdx +55 -0
- package/content/platform/store/guides/manage-the-team.mdx +55 -0
- package/content/platform/store/guides/moderate-a-store.mdx +59 -0
- package/content/platform/store/overview.mdx +86 -0
- package/content/platform/store/reference/data-models.mdx +89 -0
- package/content/platform/store/reference/events.mdx +43 -0
- package/content/platform/store/reference/links.mdx +71 -0
- package/content/platform/store/reference/service.mdx +51 -0
- package/content/platform/store/reference/workflows.mdx +35 -0
- package/content/references/api/admin/commission-rates/create-commission-rate.mdx +1 -1
- package/content/references/api/admin/commission-rates/list-commission-rates.mdx +2 -2
- package/content/references/api/admin/commission-rates/update-commission-rate.mdx +1 -1
- package/content/references/api/admin/offers/batch-create-offers.mdx +3 -3
- package/content/references/api/admin/order-groups/list-order-groups.mdx +1 -1
- package/content/references/api/admin/product-attributes/create-attribute-value.mdx +2 -2
- package/content/references/api/admin/product-attributes/create-product-attribute.mdx +1 -1
- package/content/references/api/admin/product-attributes/update-product-attribute.mdx +1 -1
- package/content/references/api/admin/product-changes/confirm-product-change.mdx +1 -1
- package/content/references/api/admin/products/batch-product-attributes.mdx +1 -1
- package/content/references/api/admin/products/create-product.mdx +1 -1
- package/content/references/api/admin/products/preview-product.mdx +1 -1
- package/content/references/api/admin.mdx +4 -5
- package/content/references/api/conventions.mdx +9 -7
- package/content/references/api/store/carts/add-line-item.mdx +1 -1
- package/content/references/api/store/offers/list-offers.mdx +1 -1
- package/content/references/api/store/order-groups/list-order-groups.mdx +1 -1
- package/content/references/api/store.mdx +4 -13
- package/content/references/api/vendor/members/accept-member-invite.mdx +1 -1
- package/content/references/api/vendor/offers/batch-create-offers.mdx +4 -0
- package/content/references/api/vendor/offers/batch-offer-inventory-items.mdx +2 -0
- package/content/references/api/vendor/offers/create-offer.mdx +13 -1
- package/content/references/api/vendor/offers/list-offers.mdx +4 -0
- package/content/references/api/vendor/offers/retrieve-offer.mdx +4 -0
- package/content/references/api/vendor/offers/update-offer.mdx +12 -0
- package/content/references/api/vendor/payout-accounts/create-onboarding.mdx +1 -1
- package/content/references/api/vendor/products/batch-product-attributes.mdx +1 -1
- package/content/references/api/vendor/products/create-product-variant.mdx +1 -1
- package/content/references/api/vendor/products/create-product.mdx +1 -1
- package/content/references/api/vendor/products/delete-product.mdx +1 -1
- package/content/references/api/vendor/products/update-product.mdx +1 -1
- package/content/references/api/vendor/sellers/create-seller.mdx +2 -2
- package/content/references/api/vendor/sellers/list-sellers.mdx +1 -1
- package/content/references/api/vendor.mdx +6 -5
- package/content/references/configuration.mdx +16 -33
- package/content/references/overview.mdx +34 -52
- package/content/references/panel-extensions/create-page.mdx +194 -0
- package/content/references/panel-extensions/custom-fields.mdx +256 -0
- package/content/references/panel-extensions/overview.mdx +102 -0
- package/content/references/panel-extensions/widgets.mdx +212 -0
- package/content/resources/ai/mcp.mdx +2 -2
- package/content/resources/ai/overview.mdx +21 -16
- package/content/resources/ai/skills.mdx +67 -0
- package/content/resources/best-practices/api-routes.mdx +55 -43
- package/content/resources/best-practices/custom-fields.mdx +116 -92
- package/content/resources/best-practices/frontend.mdx +62 -50
- package/content/resources/best-practices/module-links.mdx +48 -34
- package/content/resources/best-practices/modules.mdx +53 -27
- package/content/resources/best-practices/overview.mdx +45 -17
- package/content/resources/best-practices/subscribers-and-jobs.mdx +37 -24
- package/content/resources/best-practices/types.mdx +38 -23
- package/content/resources/best-practices/workflows.mdx +33 -21
- package/content/resources/customization/custom-fields.mdx +15 -15
- package/content/resources/customization/extend-a-workflow.mdx +7 -4
- package/content/resources/customization/extending-panels.mdx +55 -52
- package/content/resources/deployment/medusa-cloud.mdx +21 -20
- package/content/resources/deployment/self-host.mdx +123 -0
- package/content/resources/integrations/overview.mdx +38 -0
- package/content/resources/integrations/stripe-connect.mdx +39 -38
- package/content/resources/tutorials/add-a-block.mdx +25 -18
- package/content/resources/tutorials/add-a-widget.mdx +32 -23
- package/content/resources/tutorials/add-order-detail-button.mdx +33 -20
- package/content/resources/tutorials/attributes-and-variant-axes.mdx +28 -27
- package/content/resources/tutorials/build-a-block.mdx +26 -15
- package/content/resources/tutorials/custom-api-route.mdx +32 -20
- package/content/resources/tutorials/custom-panel-page.mdx +21 -12
- package/content/resources/tutorials/customize-navigation.mdx +30 -23
- package/content/resources/tutorials/extend-forms-and-tables.mdx +36 -28
- package/content/resources/tutorials/extend-onboarding.mdx +38 -35
- package/content/resources/tutorials/master-products-and-offers.mdx +28 -23
- package/content/telemetry.mdx +3 -3
- package/content/user-guide/admin/attributes/how-tos/create-an-attribute.mdx +64 -0
- package/content/user-guide/admin/attributes/how-tos/manage-possible-values.mdx +40 -0
- package/content/user-guide/admin/attributes/overview.mdx +22 -0
- package/content/user-guide/admin/commissions/how-tos/create-a-commission-rule.mdx +63 -0
- package/content/user-guide/admin/commissions/how-tos/edit-the-global-commission.mdx +48 -0
- package/content/user-guide/admin/commissions/how-tos/manage-a-commission-rule.mdx +45 -0
- package/content/user-guide/admin/commissions/overview.mdx +25 -0
- package/content/user-guide/admin/overview.mdx +20 -12
- package/content/user-guide/admin/product-requests/how-tos/review-a-new-product.mdx +58 -0
- package/content/user-guide/admin/product-requests/how-tos/review-a-product-edit.mdx +48 -0
- package/content/user-guide/admin/product-requests/overview.mdx +25 -0
- package/content/user-guide/vendor/offers/how-tos/create-an-offer.mdx +59 -0
- package/content/user-guide/vendor/offers/how-tos/update-prices-and-stock.mdx +40 -0
- package/content/user-guide/vendor/offers/overview.mdx +22 -0
- package/content/user-guide/vendor/onboarding.mdx +79 -0
- package/content/user-guide/vendor/orders/how-tos/fulfill-an-order.mdx +49 -0
- package/content/user-guide/vendor/orders/how-tos/mark-an-order-as-delivered.mdx +33 -0
- package/content/user-guide/vendor/orders/how-tos/process-a-return.mdx +42 -0
- package/content/user-guide/vendor/orders/how-tos/refund-an-order.mdx +38 -0
- package/content/user-guide/vendor/orders/how-tos/ship-an-order.mdx +40 -0
- package/content/user-guide/vendor/orders/overview.mdx +31 -0
- package/content/user-guide/vendor/overview.mdx +23 -12
- package/content/user-guide/vendor/products/how-tos/edit-a-product.mdx +44 -0
- package/content/user-guide/vendor/products/how-tos/submit-a-product.mdx +63 -0
- package/content/user-guide/vendor/products/overview.mdx +22 -0
- package/llms.txt +176 -142
- package/package.json +1 -1
- package/content/learn/concepts.mdx +0 -84
- package/content/learn/installation.mdx +0 -117
- package/content/learn/mirakl-alternative.mdx +0 -86
- package/content/migration/from-1-x-to-2-0.mdx +0 -152
- package/content/migration/from-2-0-to-2-1.mdx +0 -105
- package/content/migration/overview.mdx +0 -58
- package/content/references/api/store/search/search.mdx +0 -136
- package/content/references/modules/commission.mdx +0 -106
- package/content/references/modules/custom-fields.mdx +0 -45
- package/content/references/modules/media.mdx +0 -55
- package/content/references/modules/offer.mdx +0 -64
- package/content/references/modules/payout.mdx +0 -121
- package/content/references/modules/product-attribute.mdx +0 -111
- package/content/references/modules/product-edit.mdx +0 -80
- package/content/references/modules/search.mdx +0 -112
- package/content/references/modules/seller.mdx +0 -175
- package/content/references/panel-extension-api.mdx +0 -337
- package/content/references/workflows/cart/add-seller-shipping-method-to-cart.mdx +0 -48
- package/content/references/workflows/cart/complete-cart-with-split-orders.mdx +0 -36
- package/content/references/workflows/cart/list-seller-shipping-options-for-cart.mdx +0 -39
- package/content/references/workflows/cart/update-cart-seller-promotions.mdx +0 -42
- package/content/references/workflows/commission/batch-commission-rules.mdx +0 -49
- package/content/references/workflows/commission/create-commission-rates.mdx +0 -41
- package/content/references/workflows/commission/delete-commission-rates.mdx +0 -34
- package/content/references/workflows/commission/refresh-order-commission-lines.mdx +0 -30
- package/content/references/workflows/commission/update-commission-rates.mdx +0 -35
- package/content/references/workflows/media/set-category-images.mdx +0 -41
- package/content/references/workflows/media/set-collection-images.mdx +0 -41
- package/content/references/workflows/member/accept-member-invite.mdx +0 -33
- package/content/references/workflows/member/add-seller-member.mdx +0 -30
- package/content/references/workflows/member/create-member-invites.mdx +0 -34
- package/content/references/workflows/member/delete-member-invite.mdx +0 -24
- package/content/references/workflows/member/remove-seller-member.mdx +0 -28
- package/content/references/workflows/member/resend-member-invite.mdx +0 -28
- package/content/references/workflows/member/update-member-role.mdx +0 -28
- package/content/references/workflows/member/update-member.mdx +0 -35
- package/content/references/workflows/offer/batch-offer-inventory-items.mdx +0 -62
- package/content/references/workflows/offer/create-offers.mdx +0 -65
- package/content/references/workflows/offer/delete-offers.mdx +0 -35
- package/content/references/workflows/offer/update-offers.mdx +0 -53
- package/content/references/workflows/order/cancel-order-fulfillment.mdx +0 -34
- package/content/references/workflows/order/confirm-claim-request.mdx +0 -25
- package/content/references/workflows/order/confirm-exchange-request.mdx +0 -25
- package/content/references/workflows/order/confirm-order-edit-request.mdx +0 -25
- package/content/references/workflows/order/confirm-return-receive.mdx +0 -25
- package/content/references/workflows/order/create-order-fulfillment.mdx +0 -45
- package/content/references/workflows/order-group/get-order-group-detail.mdx +0 -29
- package/content/references/workflows/order-group/get-order-groups-list.mdx +0 -38
- package/content/references/workflows/overview.mdx +0 -72
- package/content/references/workflows/payout/create-onboarding.mdx +0 -36
- package/content/references/workflows/payout/create-payout-account.mdx +0 -33
- package/content/references/workflows/payout/create-payout.mdx +0 -30
- package/content/references/workflows/payout/process-payout-for-webhook.mdx +0 -34
- package/content/references/workflows/product/confirm-products.mdx +0 -48
- package/content/references/workflows/product/create-products.mdx +0 -63
- package/content/references/workflows/product/link-sellers-to-product-category.mdx +0 -40
- package/content/references/workflows/product/link-sellers-to-product.mdx +0 -40
- package/content/references/workflows/product/reject-product.mdx +0 -48
- package/content/references/workflows/product/request-product-change.mdx +0 -48
- package/content/references/workflows/product-attribute/add-product-attributes-to-product.mdx +0 -49
- package/content/references/workflows/product-attribute/create-and-link-product-attributes-to-product.mdx +0 -46
- package/content/references/workflows/product-attribute/create-product-attribute-values.mdx +0 -43
- package/content/references/workflows/product-attribute/create-product-attributes.mdx +0 -56
- package/content/references/workflows/product-attribute/delete-product-attribute-values.mdx +0 -30
- package/content/references/workflows/product-attribute/delete-product-attributes.mdx +0 -30
- package/content/references/workflows/product-attribute/remove-product-attributes-from-product.mdx +0 -29
- package/content/references/workflows/product-attribute/update-product-attribute-values.mdx +0 -44
- package/content/references/workflows/product-attribute/update-product-attributes-on-product.mdx +0 -41
- package/content/references/workflows/product-attribute/update-product-attributes.mdx +0 -48
- package/content/references/workflows/product-attribute/upsert-product-attribute-values.mdx +0 -43
- package/content/references/workflows/product-edit/auto-confirm-product-change.mdx +0 -39
- package/content/references/workflows/product-edit/cancel-product-change.mdx +0 -49
- package/content/references/workflows/product-edit/confirm-product-change.mdx +0 -57
- package/content/references/workflows/product-edit/create-product-change.mdx +0 -72
- package/content/references/workflows/product-edit/reject-product-change.mdx +0 -54
- package/content/references/workflows/product-edit/stage-product-change.mdx +0 -75
- package/content/references/workflows/seller/approve-seller.mdx +0 -36
- package/content/references/workflows/seller/create-seller-account.mdx +0 -59
- package/content/references/workflows/seller/create-seller-defaults.mdx +0 -22
- package/content/references/workflows/seller/create-sellers.mdx +0 -65
- package/content/references/workflows/seller/delete-seller-professional-details.mdx +0 -37
- package/content/references/workflows/seller/delete-sellers.mdx +0 -24
- package/content/references/workflows/seller/invite-seller.mdx +0 -28
- package/content/references/workflows/seller/suspend-seller.mdx +0 -37
- package/content/references/workflows/seller/terminate-seller.mdx +0 -37
- package/content/references/workflows/seller/unsuspend-seller.mdx +0 -36
- package/content/references/workflows/seller/unterminate-seller.mdx +0 -36
- package/content/references/workflows/seller/update-seller-address.mdx +0 -55
- package/content/references/workflows/seller/update-seller-payment-details.mdx +0 -52
- package/content/references/workflows/seller/update-seller-professional-details.mdx +0 -48
- package/content/references/workflows/seller/update-sellers.mdx +0 -57
- package/content/resources/ai/llms.mdx +0 -74
- package/content/resources/integrations/notifications.mdx +0 -39
- package/content/resources/integrations/search.mdx +0 -122
- package/content/resources/tutorials/configure-commissions.mdx +0 -127
- package/content/resources/tutorials/first-marketplace.mdx +0 -45
- package/content/resources/tutorials/handle-product-requests.mdx +0 -80
- package/content/resources/tutorials/import-export-products.mdx +0 -96
- package/content/resources/tutorials/seller-payouts-stripe.mdx +0 -89
- package/content/resources/tutorials/store-setup-checklist.mdx +0 -214
- package/content/tools/api-client.mdx +0 -155
- package/content/tools/cli.mdx +0 -196
- package/content/tools/dashboard-sdk.mdx +0 -35
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Frontend"
|
|
3
|
-
|
|
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.
|
|
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
|
|
9
|
+
## Use @medusajs/ui, and only it
|
|
9
10
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
91
|
+
### Read-only fields in detail sections
|
|
91
92
|
|
|
92
|
-
`displays` add read-only rows into an existing detail-page section, keyed by `id
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()
|
|
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
|
-
-
|
|
282
|
-
- Extending
|
|
283
|
-
- Read-only state
|
|
284
|
-
- New screen
|
|
285
|
-
- Data via `sdk.admin.*`
|
|
286
|
-
-
|
|
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: "
|
|
3
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
47
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
##
|
|
86
|
+
## Create links inside workflows
|
|
84
87
|
|
|
85
|
-
Links are **data**, so creating or removing one is a mutation
|
|
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
|
-
-
|
|
88
|
-
-
|
|
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
|
|
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
|
|
120
|
-
- **Filtering** by a linked module's field
|
|
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`
|
|
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
|
|
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
|
-
###
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
157
|
-
- Links are created
|
|
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: "
|
|
3
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
77
|
-
- **Models are lowercase-defined, referenced by their key.** `model.define("brand", { ... })
|
|
78
|
-
- **Types live next to the module
|
|
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()
|
|
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
|
|
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
|
|
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
|
|
110
|
+
The model itself stays flat: plain columns, no relations pointing at other modules.
|
|
102
111
|
|
|
103
|
-
```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
|
|
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
|
|
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 })
|
|
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
|
|
142
|
-
- Custom methods use `@InjectManager` / `@InjectTransactionManager`
|
|
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
|
|
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>
|