@mercurjs/docs 2.3.8-canary.2 → 2.3.8-canary.4

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 (114) hide show
  1. package/content/learn/architecture.mdx +166 -263
  2. package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
  3. package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
  4. package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
  5. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
  6. package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
  7. package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
  8. package/content/platform/attribute/overview.mdx +37 -33
  9. package/content/platform/attribute/reference/data-models.mdx +53 -38
  10. package/content/platform/attribute/reference/events.mdx +47 -24
  11. package/content/platform/attribute/reference/links.mdx +98 -27
  12. package/content/platform/attribute/reference/service.mdx +71 -32
  13. package/content/platform/attribute/reference/workflows.mdx +158 -28
  14. package/content/platform/catalog/concepts/master-products.mdx +39 -37
  15. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
  16. package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
  17. package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
  18. package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
  19. package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
  20. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
  21. package/content/platform/catalog/overview.mdx +39 -35
  22. package/content/platform/catalog/reference/data-models.mdx +154 -43
  23. package/content/platform/catalog/reference/events.mdx +41 -25
  24. package/content/platform/catalog/reference/links.mdx +196 -28
  25. package/content/platform/catalog/reference/service.mdx +57 -35
  26. package/content/platform/catalog/reference/workflows.mdx +124 -30
  27. package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
  28. package/content/platform/commission/concepts/rule-matching.mdx +53 -63
  29. package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
  30. package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
  31. package/content/platform/commission/guides/create-a-rate.mdx +101 -53
  32. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
  33. package/content/platform/commission/overview.mdx +43 -35
  34. package/content/platform/commission/reference/data-models.mdx +59 -45
  35. package/content/platform/commission/reference/events.mdx +28 -34
  36. package/content/platform/commission/reference/links.mdx +42 -33
  37. package/content/platform/commission/reference/service.mdx +103 -28
  38. package/content/platform/commission/reference/workflows.mdx +101 -27
  39. package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
  40. package/content/platform/offer/concepts/shipping.mdx +28 -29
  41. package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
  42. package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
  43. package/content/platform/offer/guides/create-an-offer.mdx +112 -60
  44. package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
  45. package/content/platform/offer/overview.mdx +39 -32
  46. package/content/platform/offer/reference/data-models.mdx +77 -47
  47. package/content/platform/offer/reference/events.mdx +44 -18
  48. package/content/platform/offer/reference/links.mdx +92 -40
  49. package/content/platform/offer/reference/service.mdx +66 -31
  50. package/content/platform/offer/reference/workflows.mdx +146 -37
  51. package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
  52. package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
  53. package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
  54. package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
  55. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
  56. package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
  57. package/content/platform/order-group/overview.mdx +42 -32
  58. package/content/platform/order-group/reference/data-models.mdx +48 -31
  59. package/content/platform/order-group/reference/events.mdx +29 -17
  60. package/content/platform/order-group/reference/links.mdx +90 -29
  61. package/content/platform/order-group/reference/service.mdx +55 -31
  62. package/content/platform/order-group/reference/workflows.mdx +83 -24
  63. package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
  64. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
  65. package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
  66. package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
  67. package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
  68. package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
  69. package/content/platform/payout/overview.mdx +43 -34
  70. package/content/platform/payout/reference/data-models.mdx +63 -36
  71. package/content/platform/payout/reference/events.mdx +56 -30
  72. package/content/platform/payout/reference/links.mdx +52 -22
  73. package/content/platform/payout/reference/service.mdx +72 -33
  74. package/content/platform/payout/reference/workflows.mdx +80 -19
  75. package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
  76. package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
  77. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
  78. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
  79. package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
  80. package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
  81. package/content/platform/product-edit/overview.mdx +41 -34
  82. package/content/platform/product-edit/reference/data-models.mdx +73 -47
  83. package/content/platform/product-edit/reference/events.mdx +39 -29
  84. package/content/platform/product-edit/reference/links.mdx +32 -24
  85. package/content/platform/product-edit/reference/service.mdx +45 -26
  86. package/content/platform/product-edit/reference/workflows.mdx +173 -31
  87. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
  88. package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
  89. package/content/platform/review/concepts/the-review-model.mdx +52 -45
  90. package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
  91. package/content/platform/review/guides/create-a-review.mdx +109 -39
  92. package/content/platform/review/guides/moderate-a-review.mdx +83 -37
  93. package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
  94. package/content/platform/review/overview.mdx +43 -33
  95. package/content/platform/review/reference/data-models.mdx +41 -27
  96. package/content/platform/review/reference/events.mdx +55 -37
  97. package/content/platform/review/reference/links.mdx +80 -22
  98. package/content/platform/review/reference/service.mdx +61 -33
  99. package/content/platform/review/reference/workflows.mdx +80 -24
  100. package/content/platform/store/concepts/lifecycle.mdx +68 -44
  101. package/content/platform/store/concepts/store-entity.mdx +45 -27
  102. package/content/platform/store/concepts/team.mdx +56 -35
  103. package/content/platform/store/guides/create-a-store.mdx +102 -35
  104. package/content/platform/store/guides/manage-the-team.mdx +122 -32
  105. package/content/platform/store/guides/moderate-a-store.mdx +107 -38
  106. package/content/platform/store/overview.mdx +41 -34
  107. package/content/platform/store/reference/data-models.mdx +132 -57
  108. package/content/platform/store/reference/events.mdx +42 -17
  109. package/content/platform/store/reference/links.mdx +67 -49
  110. package/content/platform/store/reference/service.mdx +105 -33
  111. package/content/platform/store/reference/workflows.mdx +304 -24
  112. package/content/references/panel-extensions/layouts.mdx +169 -0
  113. package/llms.txt +112 -111
  114. package/package.json +1 -1
@@ -1,31 +1,87 @@
1
1
  ---
2
- title: "Workflows"
2
+ title: "Review workflows"
3
3
  sidebarTitle: "Workflows"
4
- description: "Review workflows for creating, moderating, responding, and deleting."
4
+ description: "The four review workflows, their inputs, steps, compensation, and errors."
5
5
  ---
6
6
 
7
- This reference lists the workflows for the Review domain. Import them from
8
- `@mercurjs/core/workflows` and run them against the Medusa container.
7
+ Core ships four review workflows in `packages/core/src/workflows/review/`. Import them from `@mercurjs/core/workflows` and run them with the Medusa container. None of them exposes a workflow hook, and none emits an event of its own: the events come from the module service, as listed in the [Events reference](/platform/review/reference/events).
9
8
 
10
- ## Review workflows
9
+ ```ts
10
+ import { createReviewWorkflow } from "@mercurjs/core/workflows"
11
11
 
12
- | Workflow | Input | Purpose |
12
+ const { result: review } = await createReviewWorkflow(container).run({
13
+ input: {
14
+ order_id: "order_01JD8Q1234567",
15
+ reference: "seller",
16
+ reference_id: "sel_01JD8Q1234567",
17
+ rating: 5,
18
+ customer_id: "cus_01JD8Q1234567",
19
+ },
20
+ })
21
+ ```
22
+
23
+ ## createReviewWorkflow
24
+
25
+ Validates a submission, creates a `pending` review, and writes its customer, order, and target links. Workflow id `create-review`. Input type `CreateReviewDTO`, returns the created review.
26
+
27
+ | Field | Type | Description |
28
+ | --- | --- | --- |
29
+ | `order_id` | `string` | The order that earned the review. Must belong to `customer_id` |
30
+ | `reference` | `"product" \| "seller"` | The target type |
31
+ | `reference_id` | `string` | The product id or seller id |
32
+ | `rating` | `number` | The score. Not range checked |
33
+ | `customer_note` | `string \| null` | Optional. Defaults to `null` |
34
+ | `customer_id` | `string` | The customer writing the review |
35
+
36
+ The workflow runs three steps:
37
+
38
+ 1. **`validate-review`**: fails with `INVALID_DATA` “Order not found!” when no order matches `order_id` and `customer_id`, and with “Review already exists” when the order has a review with the same `reference` and target id
39
+ 2. **`create-review`**: creates the review and links it to the customer and the order; on rollback, deletes the review
40
+ 3. **`create-remote-links`**: Medusa’s `createRemoteLinkStep`, which links the review to the product or the seller
41
+
42
+ ## updateReviewWorkflow
43
+
44
+ Updates any of a review’s editable fields, including `status`. Workflow id `update-review`. Input type `UpdateReviewDTO`, returns the updated review.
45
+
46
+ | Field | Type | Description |
47
+ | --- | --- | --- |
48
+ | `id` | `string` | The review id |
49
+ | `rating` | `number` | Optional |
50
+ | `customer_note` | `string \| null` | Optional |
51
+ | `seller_note` | `string \| null` | Optional. Pass `null` to clear an answer |
52
+ | `status` | `"pending" \| "published" \| "rejected"` | Optional. Any value from any status |
53
+
54
+ Its single step, `update-review`, reads the review before writing. On rollback it restores the previous `rating`, `customer_note`, `seller_note`, and `status`.
55
+
56
+ ## respondReviewWorkflow
57
+
58
+ Writes a store’s answer to `seller_note`, once. Workflow id `respond-review`. Input type `RespondReviewDTO`, returns the updated review.
59
+
60
+ | Field | Type | Description |
61
+ | --- | --- | --- |
62
+ | `id` | `string` | The review id |
63
+ | `seller_note` | `string` | The answer |
64
+
65
+ Its single step, `respond-review`, fails with `NOT_FOUND` “Review not found” for an unknown id and with `NOT_ALLOWED` “This review already has a response” when `seller_note` is set. On rollback it sets `seller_note` back to `null`. The workflow doesn’t check which store owns the review: the Vendor API checks the seller link before running it.
66
+
67
+ ## deleteReviewWorkflow
68
+
69
+ Soft deletes a review. Workflow id `delete-review`. The input is the review id as a plain string, and the workflow returns that id.
70
+
71
+ ```ts
72
+ await deleteReviewWorkflow(container).run({ input: "rev_01JD8Q1234567" })
73
+ ```
74
+
75
+ Its single step, `delete-review`, calls `softDeleteReviews` and leaves the review’s links in place. On rollback it calls `restoreReviews`.
76
+
77
+ ## Steps
78
+
79
+ `@mercurjs/core/workflows` also exports each step, so you can compose them into your own workflows:
80
+
81
+ | Step | Id | Input |
13
82
  | --- | --- | --- |
14
- | `createReviewWorkflow` | `{ order_id, reference, reference_id, rating, customer_note?, customer_id }` | Validate and create a review, linking it to its target, order, and customer |
15
- | `updateReviewWorkflow` | `{ id, rating?, customer_note?, seller_note?, status? }` | Update a review, including moderating its `status` |
16
- | `respondReviewWorkflow` | `{ id, seller_note }` | Attach a store's single response to a review |
17
- | `deleteReviewWorkflow` | `id` | Soft-delete a review (restorable on rollback) |
18
-
19
- Each workflow supports compensation: `createReviewWorkflow` deletes the review it
20
- created on failure, `updateReviewWorkflow` and `respondReviewWorkflow` restore the
21
- previous values, and `deleteReviewWorkflow` restores the soft-deleted row.
22
-
23
- <Note>
24
- `createReviewWorkflow` validates that the order belongs to the customer and
25
- that the target hasn't already been reviewed on that order. `respondReviewWorkflow`
26
- refuses to overwrite an existing response.
27
- </Note>
28
-
29
- To work with records directly instead of through a workflow, see the
30
- [Service reference](/platform/review/reference/service). To run side effects when
31
- a review changes, see the [Event reference](/platform/review/reference/events).
83
+ | `validateReviewStep` | `validate-review` | `CreateReviewDTO` |
84
+ | `createReviewStep` | `create-review` | `CreateReviewDTO` |
85
+ | `updateReviewStep` | `update-review` | `UpdateReviewDTO` |
86
+ | `respondReviewStep` | `respond-review` | `RespondReviewDTO` |
87
+ | `deleteReviewStep` | `delete-review` | `string` |
@@ -1,62 +1,86 @@
1
1
  ---
2
- title: "Lifecycle"
2
+ title: "Store statuses and transitions"
3
3
  sidebarTitle: "Lifecycle"
4
- description: "Store statuses, transitions, scheduled closures, and premium."
4
+ description: "The four store statuses, the workflow behind each transition, scheduled closures, and the premium flag."
5
5
  ---
6
6
 
7
- This page covers the store account lifecycle and the states a store moves through.
7
+ Every store has a status that decides whether it can trade. The status lives in the `status` field of `Seller`, typed by the `SellerStatus` enum, and only dedicated workflows move it. Each transition validates the current status, records a reason or a timestamp, emits an event, and exposes a hook you can extend.
8
8
 
9
- ## Status
9
+ ## The four statuses
10
10
 
11
- A store's state lives in the `status` field of the `Seller` model, typed by the
12
- `SellerStatus` enum. A store moves through four statuses.
13
-
14
- ```
15
- ┌───────────────────┐
16
- │ pending_approval │
17
- └─────────┬─────────┘
18
- │ approve
19
- ▼
20
- ┌───────────┐ ┌────────┐
21
- │ suspended │◄───►│ open │
22
- └───────────┘ └───┬────┘
23
- │ terminate
24
- ▼
25
- ┌────────────┐
26
- │ terminated │
27
- └────────────┘
28
- ```
11
+ A store is in exactly one status at a time. New stores start in `pending_approval` unless you pass another status to `createSellersWorkflow`.
29
12
 
30
13
  | Status | Meaning |
31
14
  | --- | --- |
32
- | `pending_approval` | Registered, waiting for operator review. |
33
- | `open` | Active. Can list offers, take orders, and collect payouts. |
34
- | `suspended` | Temporarily frozen. Offers stay listed but cannot be purchased. |
35
- | `terminated` | Permanently closed. This status is irreversible. |
15
+ | `pending_approval` | Registered and waiting for operator review |
16
+ | `open` | Trading: listed in the Store API with its offers |
17
+ | `suspended` | Frozen by the operator, hidden from the Store API |
18
+ | `terminated` | Closed by the operator, hidden from the Store API and from its members’ store list |
36
19
 
37
- Only the operator can change a store's status. Each transition runs through a
38
- dedicated workflow: `approveSellerWorkflow`, `suspendSellerWorkflow`,
39
- `unsuspendSellerWorkflow`, and `terminateSellerWorkflow`.
20
+ The Store API seller and offer listings return only `open` stores that aren’t inside a closure window. A store in any other status disappears from those listings, but its records stay intact.
40
21
 
41
- <Note>
42
- Termination is irreversible. All orders and payouts must be resolved before a
43
- store can be terminated.
44
- </Note>
22
+ ## Transitions
23
+
24
+ Each transition is its own workflow, and each one checks the starting status before it writes.
25
+
26
+ <img className="block dark:hidden" src="/images/figures/store-lifecycle-light.svg" alt="A Status transitions table for the Seller module listing each lifecycle workflow with the status it starts from, the status it sets, and the event it emits, with the terminate row highlighted because only a suspended or pending store can be terminated." />
27
+ <img className="hidden dark:block" src="/images/figures/store-lifecycle-dark.svg" alt="A Status transitions table for the Seller module listing each lifecycle workflow with the status it starts from, the status it sets, and the event it emits, with the terminate row highlighted because only a suspended or pending store can be terminated." />
28
+
29
+ The workflows accept these starting statuses:
30
+
31
+ | Workflow | Allowed from | Sets |
32
+ | --- | --- | --- |
33
+ | `approveSellerWorkflow` | `pending_approval` | `open`, and `approved_at` on first approval |
34
+ | `suspendSellerWorkflow` | `pending_approval`, `open` | `suspended`, `status_reason`, and `rejected_at` on first suspension |
35
+ | `unsuspendSellerWorkflow` | `suspended` | `open`, and clears `status_reason` |
36
+ | `terminateSellerWorkflow` | `pending_approval`, `suspended` | `terminated` and `status_reason` |
37
+ | `unterminateSellerWorkflow` | `terminated` | `suspended`, and clears `status_reason` |
38
+
39
+ Any other starting status fails with a `NOT_ALLOWED` error and changes nothing.
40
+
41
+ <Warning>
42
+ You can’t terminate an `open` store. Suspend it first, then terminate it.
43
+ </Warning>
44
+
45
+ Termination isn’t final: `unterminateSellerWorkflow` brings a store back as `suspended`, and you then unsuspend it to reopen. Use suspension to reject a pending application: it stamps `rejected_at` and keeps the reason in `status_reason`.
45
46
 
46
47
  ## Scheduled closures
47
48
 
48
- A store can schedule a temporary closure with the `closed_from` and `closed_to`
49
- fields without changing its status. During the window, the storefront shows as
50
- unavailable and no new orders are accepted. The account stays `open` and resumes
51
- on its own once `closed_to` passes.
49
+ A scheduled closure takes a store offline for a window without changing its status. Set `closed_from` and `closed_to`, and optionally a `closure_note` for the reason. Both the operator and the store’s own members can set these fields.
52
50
 
53
- <Tip>
54
- A closure overlays the current status. It is not a status of its own, and it
55
- does not affect the transition rules.
56
- </Tip>
51
+ The Store API filter shows a store only when both bounds allow it:
52
+
53
+ - **`closed_from`**: `null`, or later than now
54
+ - **`closed_to`**: `null`, or earlier than now
55
+
56
+ The store comes back once `closed_to` passes, with no job or workflow involved. Set the fields as the closure starts, for example from a scheduled job, because a future `closed_to` hides the store right away even when `closed_from` is also in the future.
57
+
58
+ ```ts
59
+ import { updateSellersWorkflow } from "@mercurjs/core/workflows"
60
+
61
+ await updateSellersWorkflow(container).run({
62
+ input: {
63
+ selector: { id: "sel_01JD8Q1234567" },
64
+ update: {
65
+ closed_from: new Date(),
66
+ closed_to: new Date("2026-12-27"),
67
+ },
68
+ },
69
+ })
70
+ ```
71
+
72
+ This closes the store from now until December 27. A `closed_from` in the past with a `null` `closed_to` hides the store until you clear the fields.
73
+
74
+ <Note>
75
+ Closures filter the Store API seller and offer listings. They don’t block cart or checkout workflows, so add your own check there if a closed store must refuse orders.
76
+ </Note>
57
77
 
58
78
  ## Premium
59
79
 
60
- The `is_premium` boolean is set only by the operator. Stores cannot designate
61
- themselves as premium. The storefront uses the flag for featured placement,
62
- badges, and curation priority.
80
+ `is_premium` is a boolean that defaults to `false`. The Admin API accepts it on create and update, and the Vendor API doesn’t, so only the operator can set it. Mercur stores the flag and returns it, and your storefront decides what premium means, such as featured placement or a badge.
81
+
82
+ ## Related
83
+
84
+ - [Moderate stores](/platform/store/guides/moderate-a-store)
85
+ - [Event reference](/platform/store/reference/events)
86
+ - [Workflows reference](/platform/store/reference/workflows)
@@ -1,19 +1,27 @@
1
1
  ---
2
- title: "The store entity"
2
+ title: "What a store is made of"
3
3
  sidebarTitle: "The store"
4
- description: "The seller record, its business identity, and its satellite data."
4
+ description: "The seller record, its currency and regions, and the satellite records that hold its business details."
5
5
  ---
6
6
 
7
- This page covers the store record and the data models that make up its business
8
- identity.
7
+ A store is the `Seller` record: the account a seller trades under on your marketplace. It holds the public profile customers see, the single currency the store settles in, and its status. Three satellite records carry its business details: an address, professional details, and bank payment details.
9
8
 
10
- ## Store
9
+ ## The seller record
11
10
 
12
- A store is the marketplace vendor. It owns offers, orders, and payouts, and it
13
- holds the profile shown to customers on the storefront. A store is represented by
14
- the `Seller` data model (table `seller`, id prefix `sel`).
11
+ The `Seller` data model (table `seller`, id prefix `sel`) is the root of everything a store owns. Offers, orders, payouts, and price lists link to it from their own modules.
12
+
13
+ A seller has four unique fields besides its id:
14
+
15
+ - **`name`**: unique among stores that aren’t deleted
16
+ - **`handle`**: unique, URL safe, and generated from `name` when you omit it
17
+ - **`email`**: unique, the store’s contact address
18
+ - **`external_id`**: optional, unique when set, for mapping to an outside system
19
+
20
+ The service validates `handle` on create and update. A handle with characters that aren’t URL safe fails with an `INVALID_DATA` error.
15
21
 
16
22
  ```ts
23
+ import { createSellersWorkflow } from "@mercurjs/core/workflows"
24
+
17
25
  const { result } = await createSellersWorkflow(container).run({
18
26
  input: {
19
27
  sellers: [
@@ -21,33 +29,43 @@ const { result } = await createSellersWorkflow(container).run({
21
29
  name: "Acme Supplies",
22
30
  email: "team@acme.com",
23
31
  currency_code: "usd",
32
+ member: { email: "owner@acme.com" },
24
33
  },
25
34
  ],
26
35
  },
27
36
  })
28
37
  ```
29
38
 
30
- <Tip>
31
- A store settles in exactly one currency, set by `currency_code`. A seller who
32
- needs to trade in several currencies creates a separate store for each one, and
33
- each store operates independently.
34
- </Tip>
39
+ This creates a store with the handle `acme-supplies` and the status `pending_approval`. It also invites `owner@acme.com` to join the team as an administrator.
40
+
41
+ ## Currency and regions
42
+
43
+ A store settles in one currency, set by `currency_code` when you create it. `UpdateSellerDTO` has no `currency_code` field, so the currency stays fixed for the life of the store. The vendor request context carries it as `seller_context.currency_code`.
44
+
45
+ A store also links to one or more regions through the `region_seller` link:
46
+
47
+ - **On create**: the store links to the default region of your Medusa store, plus any `region_ids` you pass
48
+ - **On change**: `linkSellerRegionsWorkflow` adds and removes region links in one call
49
+ - **Rule**: a store must keep at least one region, so removing the last one fails
50
+
51
+ ## Business details
52
+
53
+ Three one-to-one records hang off the seller. Each workflow that writes them creates the record when it’s missing and updates it otherwise.
54
+
55
+ | Model | Holds | Written by |
56
+ | --- | --- | --- |
57
+ | `SellerAddress` | The store’s postal address and contact name | `updateSellerAddressWorkflow` |
58
+ | `ProfessionalDetails` | Corporate name, registration number, tax id | `updateSellerProfessionalDetailsWorkflow` |
59
+ | `PaymentDetails` | Bank account holder, IBAN, BIC, routing and account numbers | `updateSellerPaymentDetailsWorkflow` |
35
60
 
36
- ## Professional details
61
+ You remove professional details with `deleteSellerProfessionalDetailsWorkflow`. Deleting a seller cascades to all three records and to its pending invites, and detaches its members without deleting them.
37
62
 
38
- A store can carry professional details: a corporate name, a registration number,
39
- and a tax id. These are represented by the `ProfessionalDetails` data model. When
40
- this record is present, the store is a registered business. When it is absent,
41
- the store is an individual seller.
63
+ ## Fulfillment defaults
42
64
 
43
- ## Addresses and payment details
65
+ `default_leadtime_to_ship` holds the number of days a store needs before it ships, and defaults to `2`. Vendors can change it on their own store.
44
66
 
45
- A store keeps its addresses as `SellerAddress` records, one each for a warehouse,
46
- return, or business address. The bank details used to settle payouts live on a
47
- `PaymentDetails` record. Both are satellite records of the store, and fulfillment
48
- and payout settlement read from them.
67
+ ## Related
49
68
 
50
- <Note>
51
- `ProfessionalDetails`, `SellerAddress`, and `PaymentDetails` are one-to-one with
52
- the store and are deleted along with it.
53
- </Note>
69
+ - [Team & members](/platform/store/concepts/team)
70
+ - [Lifecycle](/platform/store/concepts/lifecycle)
71
+ - [Data models reference](/platform/store/reference/data-models)
@@ -1,50 +1,71 @@
1
1
  ---
2
- title: "Team & members"
2
+ title: "How store teams work"
3
3
  sidebarTitle: "Team & members"
4
- description: "Members, roles, invites, and the many-to-many store relationship."
4
+ description: "Members, memberships, owners, roles, invites, and how a member picks the store they act for."
5
5
  ---
6
6
 
7
- This page covers how a store's team is modeled and how access is structured.
7
+ A store’s team is the set of people who can sign in to the Vendor panel and act for that store. Mercur models the person and the membership separately, so one person can work for several stores with a different role in each. This matters as soon as an agency, a franchise, or a holding company runs more than one store on your marketplace.
8
8
 
9
- ## Member
9
+ ## Members and memberships
10
10
 
11
- A member is a dashboard user who can manage one or more stores. A member is
12
- represented by the `Member` data model (table `member`, id prefix `mem`). Their
13
- identity is tied to their `email`, which is unique across the system.
11
+ A `Member` (id prefix `mem`) is a person. Their `email` is unique across the whole marketplace, so the same person never has two member records. A member can be deactivated with `is_active` without touching their memberships.
14
12
 
15
- ## Seller member
13
+ A `SellerMember` (id prefix `selmem`) is one person’s membership in one store. It’s the pivot between `Seller` and `Member`, and it’s unique on `(seller_id, member_id)`. Each membership carries:
16
14
 
17
- The relationship between stores and members is many-to-many. A store can have
18
- several members, and a member can belong to several stores. Each membership is a
19
- `SellerMember` record, the pivot between `Seller` and `Member`, unique on
20
- `(seller_id, member_id)`.
15
+ - **`role_id`**: the member’s role in this store, one of the `SellerRole` values
16
+ - **`is_owner`**: `true` for the store’s owner membership, `false` for everyone else
17
+ - **`metadata`**: free-form data for your own extensions
21
18
 
22
- ```ts
23
- await addSellerMemberWorkflow(container).run({
24
- input: {
25
- seller_id: "sel_123",
26
- member: { email: "ops@acme.com" },
27
- },
28
- })
29
- ```
19
+ ## Roles
20
+
21
+ `SellerRole` from `@mercurjs/types` lists the five roles a membership can hold. The membership stores the role as a plain string.
22
+
23
+ | `SellerRole` key | Stored value |
24
+ | --- | --- |
25
+ | `SELLER_ADMINISTRATION` | `role_seller_administration` |
26
+ | `INVENTORY_MANAGEMENT` | `role_seller_inventory_management` |
27
+ | `ORDER_MANAGEMENT` | `role_seller_order_management` |
28
+ | `ACCOUNTING` | `role_seller_accounting` |
29
+ | `SUPPORT` | `role_seller_support` |
30
+
31
+ The vendor membership check confirms that a member belongs to the store they’re acting for. It doesn’t restrict routes by role, so enforce role rules in your own middleware when you need them.
32
+
33
+ ## The owner
34
+
35
+ Every store has at most one owner membership, flagged with `is_owner`. Mercur sets it in two places:
36
+
37
+ - **Self-registration**: `createSellerAccountWorkflow` adds the registering member as owner with `role_seller_administration`
38
+ - **First accepted invite**: `acceptMemberInviteWorkflow` makes the new member owner when the store has none yet
30
39
 
31
- Every membership carries a `role_id`, resolved against the RBAC roles module, and
32
- an `is_owner` flag.
40
+ `updateMemberRoleWorkflow` and `removeSellerMemberWorkflow` both reject the owner membership with a `NOT_ALLOWED` error. That keeps every store with someone who can administer it.
33
41
 
34
- <Tip>
35
- A store must always keep at least one member with admin access. The last admin
36
- cannot be removed or demoted.
37
- </Tip>
42
+ ## Invites
38
43
 
39
- ## Member invite
44
+ A `MemberInvite` (id prefix `meminv`) is a pending offer to join one store with one role. The service signs a JWT for each invite, stores it in `token`, and sets `expires_at` 7 days out unless you change the module’s `invite_valid_duration` option.
45
+
46
+ Invites follow these rules:
47
+
48
+ - **One open invite per email and store**: a unique index covers `(email, seller_id)` while `accepted` is `false`
49
+ - **No invites for existing members**: inviting an email that already belongs to the store fails
50
+ - **Existing accounts are reused**: accepting an invite for a known email links the existing member instead of creating one
51
+ - **Single use**: accepting deletes the invite, and an expired or accepted token fails validation
52
+
53
+ ## The active store
54
+
55
+ A member who belongs to several stores picks the active one per request. The vendor middleware reads the store id from the `x-seller-id` header, or from the session, and checks for a matching `SellerMember`. It then exposes the store on `req.seller_context`, typed by the `SellerContext` interface:
56
+
57
+ ```ts
58
+ interface SellerContext {
59
+ seller_id: string
60
+ currency_code: string
61
+ seller_member: SellerMemberDTO
62
+ }
63
+ ```
40
64
 
41
- Pending invitations are represented by the `MemberInvite` data model. If an
42
- invited email already belongs to a user, accepting the invite links the new store
43
- association to their existing account instead of creating a new one.
65
+ Every vendor route scopes its reads and writes to `seller_context.seller_id`, so switching stores in the Vendor panel never shows another store’s data. The store list a member sees leaves out terminated stores.
44
66
 
45
- ## Store switcher
67
+ ## Related
46
68
 
47
- A user who belongs to several stores uses the store switcher in the Vendor panel
48
- to change the active store. Each store context is fully isolated. Switching stores
49
- shows only that store's offers, orders, and payouts, and it never leaks data
50
- across stores.
69
+ - [Manage the team](/platform/store/guides/manage-the-team)
70
+ - [Workflows reference](/platform/store/reference/workflows)
71
+ - [What a store is made of](/platform/store/concepts/store-entity)
@@ -1,55 +1,122 @@
1
1
  ---
2
- title: "Create a store"
2
+ title: "Create a store from server code"
3
3
  sidebarTitle: "Create a store"
4
- description: "Create a seller programmatically with createSellersWorkflow."
4
+ description: "Create stores with createSellersWorkflow from a route, a seed script, or an import job, and extend creation with hooks."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to create a store from your own server code. This
8
- is useful in a seed script, a custom API route, or an onboarding flow.
7
+ Create a store from your own server code when you seed a marketplace, import sellers from another system, or build a custom onboarding flow. `createSellersWorkflow` creates the `Seller`, links its regions, and invites its first team member.
9
8
 
10
- Mercur exposes a `createSellersWorkflow` that creates the `Seller` record along
11
- with its defaults. Run it from any place that has access to the Medusa container.
9
+ ## Before you begin
12
10
 
13
- ## Run the workflow
11
+ You need the following in place:
14
12
 
15
- ```ts title="src/api/custom/route.ts"
16
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
17
- import { createSellersWorkflow } from "@mercurjs/core/workflows"
13
+ - A Mercur server with `@mercurjs/core` installed through `withMercur()`
14
+ - A default region set on your Medusa store, since every new store links to it
15
+
16
+ ## Create the store
17
+
18
+ <Steps>
19
+ <Step title="Add a route that runs the workflow">
20
+ Create an API route and run the workflow against the request’s container. Each entry in `sellers` needs a `member.email`: the workflow sends that address an invite with the `role_seller_administration` role.
18
21
 
19
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
20
- const { result } = await createSellersWorkflow(req.scope).run({
21
- input: {
22
- sellers: [
23
- {
24
- name: "Acme Supplies",
25
- email: "team@acme.com",
26
- currency_code: "usd",
22
+ ```ts title="src/api/admin/import-sellers/route.ts"
23
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
24
+ import { createSellersWorkflow } from "@mercurjs/core/workflows"
25
+
26
+ export async function POST(req: MedusaRequest, res: MedusaResponse) {
27
+ const { result: sellers } = await createSellersWorkflow(req.scope).run({
28
+ input: {
29
+ sellers: [
30
+ {
31
+ name: "Acme Supplies",
32
+ email: "team@acme.com",
33
+ currency_code: "usd",
34
+ member: { email: "owner@acme.com" },
35
+ region_ids: ["reg_01JD8Q1234567"],
36
+ },
37
+ ],
27
38
  },
28
- ],
29
- },
30
- })
39
+ })
40
+
41
+ res.status(201).json({ sellers })
42
+ }
43
+ ```
44
+
45
+ The store gets the handle `acme-supplies` and links to the default region plus `reg_01JD8Q1234567`.
46
+ </Step>
47
+ <Step title="Open the store">
48
+ New stores start in `pending_approval`. Run `approveSellerWorkflow` to open one, or pass `status: "open"` in the seller input when you import stores you’ve already vetted.
49
+
50
+ ```ts
51
+ import { approveSellerWorkflow } from "@mercurjs/core/workflows"
52
+
53
+ await approveSellerWorkflow(req.scope).run({
54
+ input: { seller_id: sellers[0].id },
55
+ })
56
+ ```
57
+
58
+ Approval stamps `approved_at` and emits `seller.approved`.
59
+ </Step>
60
+ <Step title="Let the owner accept the invite">
61
+ The workflow emits `member_invite.created` with the invite’s `id`, `token`, and `expires_at`. Send the token to the invitee from a subscriber. When they accept it through `acceptMemberInviteWorkflow`, they join as the store’s owner. [Manage the team](/platform/store/guides/manage-the-team) shows both the subscriber and the accept call.
62
+ </Step>
63
+ </Steps>
64
+
65
+ ## Attach custom data with additional_data
31
66
 
32
- res.status(201).json({ seller: result[0] })
33
- }
67
+ The workflow passes `additional_data` to its `sellersCreated` hook. Use the hook to save data that belongs to your extension, such as a referral code, without changing the workflow.
68
+
69
+ ```ts title="src/workflows/hooks/sellers-created.ts"
70
+ import { createSellersWorkflow } from "@mercurjs/core/workflows"
71
+
72
+ createSellersWorkflow.hooks.sellersCreated(
73
+ async ({ sellers, additional_data }, { container }) => {
74
+ const referralCode = additional_data?.referral_code
75
+ if (!referralCode) {
76
+ return
77
+ }
78
+ const logger = container.resolve("logger")
79
+ logger.info(`${sellers.length} sellers joined via ${referralCode}`)
80
+ }
81
+ )
34
82
  ```
35
83
 
36
- <Note>
37
- An operator can set a new store to `open` right away. A store created through
38
- the public self-registration flow starts in `pending_approval` and waits for
39
- operator review. See [Moderate stores](/platform/store/guides/moderate-a-store).
40
- </Note>
84
+ Pass `additional_data: { referral_code: "SPRING25" }` next to `sellers` in the workflow input to reach this hook. The workflow also has a `validate` hook that runs before anything is written, so you can throw there to reject the input.
41
85
 
42
- ## Attach custom data
86
+ ## Create the owner membership directly
43
87
 
44
- The workflow accepts an `additional_data` payload. It is passed to the workflow's
45
- hooks, so you can persist marketplace-specific data alongside the store without
46
- forking the workflow.
88
+ `createSellersWorkflow` invites the owner. When the person already signed in, for example in a custom registration route, run `createSellerAccountWorkflow` instead. It creates the store in `pending_approval` and adds the member as owner in one call.
47
89
 
48
90
  ```ts
49
- await createSellersWorkflow(req.scope).run({
91
+ import { createSellerAccountWorkflow } from "@mercurjs/core/workflows"
92
+
93
+ const { result: seller } = await createSellerAccountWorkflow(
94
+ req.scope
95
+ ).run({
50
96
  input: {
51
- sellers: [{ name: "Acme Supplies", email: "team@acme.com", currency_code: "usd" }],
52
- additional_data: { referral_code: "SPRING25" },
97
+ auth_identity_id: req.auth_context.auth_identity_id,
98
+ member_email: "owner@acme.com",
99
+ seller: {
100
+ name: "Acme Supplies",
101
+ email: "team@acme.com",
102
+ currency_code: "usd",
103
+ },
53
104
  },
54
105
  })
55
106
  ```
107
+
108
+ The workflow upserts the member by email and stores its id on the auth identity under the `member` actor type. It also accepts `address`, `professional_details`, and `payment_details`, and writes each one when present.
109
+
110
+ ## Handle a failed creation
111
+
112
+ The workflow rolls back every step when one fails. These inputs fail:
113
+
114
+ - **Duplicate `name`, `email`, `handle`, or `external_id`**: the unique indexes reject the insert
115
+ - **Invalid `handle`**: a handle with characters that aren’t URL safe fails with `INVALID_DATA`
116
+ - **Unknown region**: a `region_ids` entry that doesn’t exist fails with `NOT_FOUND`
117
+
118
+ ## Next steps
119
+
120
+ - [Moderate stores](/platform/store/guides/moderate-a-store)
121
+ - [Manage the team](/platform/store/guides/manage-the-team)
122
+ - [Workflows reference](/platform/store/reference/workflows)