@mercurjs/docs 2.3.8-canary.3 → 2.3.9-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/content/learn/architecture.mdx +166 -263
  2. package/content/learn/introduction.mdx +9 -1
  3. package/content/learn/use-cases.mdx +70 -0
  4. package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
  5. package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
  6. package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
  7. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
  8. package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
  9. package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
  10. package/content/platform/attribute/overview.mdx +37 -33
  11. package/content/platform/attribute/reference/data-models.mdx +53 -38
  12. package/content/platform/attribute/reference/events.mdx +47 -24
  13. package/content/platform/attribute/reference/links.mdx +98 -27
  14. package/content/platform/attribute/reference/service.mdx +71 -32
  15. package/content/platform/attribute/reference/workflows.mdx +158 -28
  16. package/content/platform/catalog/concepts/master-products.mdx +39 -37
  17. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
  18. package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
  19. package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
  20. package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
  21. package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
  22. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
  23. package/content/platform/catalog/overview.mdx +39 -35
  24. package/content/platform/catalog/reference/data-models.mdx +154 -43
  25. package/content/platform/catalog/reference/events.mdx +41 -25
  26. package/content/platform/catalog/reference/links.mdx +196 -28
  27. package/content/platform/catalog/reference/service.mdx +57 -35
  28. package/content/platform/catalog/reference/workflows.mdx +124 -30
  29. package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
  30. package/content/platform/commission/concepts/rule-matching.mdx +53 -63
  31. package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
  32. package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
  33. package/content/platform/commission/guides/create-a-rate.mdx +101 -53
  34. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
  35. package/content/platform/commission/overview.mdx +43 -35
  36. package/content/platform/commission/reference/data-models.mdx +59 -45
  37. package/content/platform/commission/reference/events.mdx +28 -34
  38. package/content/platform/commission/reference/links.mdx +42 -33
  39. package/content/platform/commission/reference/service.mdx +103 -28
  40. package/content/platform/commission/reference/workflows.mdx +101 -27
  41. package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
  42. package/content/platform/offer/concepts/shipping.mdx +28 -29
  43. package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
  44. package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
  45. package/content/platform/offer/guides/create-an-offer.mdx +112 -60
  46. package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
  47. package/content/platform/offer/overview.mdx +39 -32
  48. package/content/platform/offer/reference/data-models.mdx +77 -47
  49. package/content/platform/offer/reference/events.mdx +44 -18
  50. package/content/platform/offer/reference/links.mdx +92 -40
  51. package/content/platform/offer/reference/service.mdx +66 -31
  52. package/content/platform/offer/reference/workflows.mdx +146 -37
  53. package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
  54. package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
  55. package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
  56. package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
  57. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
  58. package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
  59. package/content/platform/order-group/overview.mdx +42 -32
  60. package/content/platform/order-group/reference/data-models.mdx +48 -31
  61. package/content/platform/order-group/reference/events.mdx +29 -17
  62. package/content/platform/order-group/reference/links.mdx +90 -29
  63. package/content/platform/order-group/reference/service.mdx +55 -31
  64. package/content/platform/order-group/reference/workflows.mdx +83 -24
  65. package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
  66. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
  67. package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
  68. package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
  69. package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
  70. package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
  71. package/content/platform/payout/overview.mdx +43 -34
  72. package/content/platform/payout/reference/data-models.mdx +63 -36
  73. package/content/platform/payout/reference/events.mdx +56 -30
  74. package/content/platform/payout/reference/links.mdx +52 -22
  75. package/content/platform/payout/reference/service.mdx +72 -33
  76. package/content/platform/payout/reference/workflows.mdx +80 -19
  77. package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
  78. package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
  79. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
  80. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
  81. package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
  82. package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
  83. package/content/platform/product-edit/overview.mdx +41 -34
  84. package/content/platform/product-edit/reference/data-models.mdx +73 -47
  85. package/content/platform/product-edit/reference/events.mdx +39 -29
  86. package/content/platform/product-edit/reference/links.mdx +32 -24
  87. package/content/platform/product-edit/reference/service.mdx +45 -26
  88. package/content/platform/product-edit/reference/workflows.mdx +173 -31
  89. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
  90. package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
  91. package/content/platform/review/concepts/the-review-model.mdx +52 -45
  92. package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
  93. package/content/platform/review/guides/create-a-review.mdx +109 -39
  94. package/content/platform/review/guides/moderate-a-review.mdx +83 -37
  95. package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
  96. package/content/platform/review/overview.mdx +43 -33
  97. package/content/platform/review/reference/data-models.mdx +41 -27
  98. package/content/platform/review/reference/events.mdx +55 -37
  99. package/content/platform/review/reference/links.mdx +80 -22
  100. package/content/platform/review/reference/service.mdx +61 -33
  101. package/content/platform/review/reference/workflows.mdx +80 -24
  102. package/content/platform/store/concepts/lifecycle.mdx +68 -44
  103. package/content/platform/store/concepts/store-entity.mdx +45 -27
  104. package/content/platform/store/concepts/team.mdx +56 -35
  105. package/content/platform/store/guides/create-a-store.mdx +102 -35
  106. package/content/platform/store/guides/manage-the-team.mdx +122 -32
  107. package/content/platform/store/guides/moderate-a-store.mdx +107 -38
  108. package/content/platform/store/overview.mdx +41 -34
  109. package/content/platform/store/reference/data-models.mdx +132 -57
  110. package/content/platform/store/reference/events.mdx +42 -17
  111. package/content/platform/store/reference/links.mdx +67 -49
  112. package/content/platform/store/reference/service.mdx +105 -33
  113. package/content/platform/store/reference/workflows.mdx +304 -24
  114. package/content/references/api/conventions.mdx +20 -0
  115. package/content/resources/integrations/magento2.mdx +162 -0
  116. package/content/resources/integrations/overview.mdx +10 -0
  117. package/content/resources/integrations/stripe-connect.mdx +1 -1
  118. package/content/resources/tutorials/custom-api-route.mdx +3 -2
  119. package/llms.txt +113 -111
  120. package/package.json +1 -1
@@ -1,22 +1,59 @@
1
1
  ---
2
- title: "Event reference"
2
+ title: "Attribute events"
3
3
  sidebarTitle: "Events"
4
- description: "Events emitted by the Attribute domain, for subscribers and side effects."
4
+ description: "Events the Product Attribute workflows emit, when they fire, and their payloads."
5
5
  ---
6
6
 
7
- The Attribute domain emits events as attributes and their values change.
8
- Subscribe to them to run side effects, such as reindexing storefront filters,
9
- syncing an external PIM, or kicking off follow-up workflows, instead of polling.
7
+ The catalog workflows emit 6 events as attributes and values change. Subscribe to them to reindex storefront facets, sync an external product information system, or start your own workflows. The event names are exported as `ProductAttributeWorkflowEvents` and `ProductAttributeValueWorkflowEvents` from `@mercurjs/core/workflows`.
10
8
 
11
- ```ts title="src/subscribers/attribute-created.ts"
9
+ ## Attribute events
10
+
11
+ Each event fires once per attribute, with that attribute’s id as the payload.
12
+
13
+ | Event | Emitted when | Payload |
14
+ | --- | --- | --- |
15
+ | `product-attribute.created` | `createProductAttributesWorkflow` creates attributes | `{ id }` |
16
+ | `product-attribute.updated` | `updateProductAttributesWorkflow` updates attributes, including an inline rename from the batch workflow | `{ id }` |
17
+ | `product-attribute.deleted` | `deleteProductAttributesWorkflow` deletes attributes, including inline attributes removed from their product | `{ id }` |
18
+
19
+ ## Value events
20
+
21
+ Each event fires once per value, with that value’s id as the payload.
22
+
23
+ | Event | Emitted when | Payload |
24
+ | --- | --- | --- |
25
+ | `product-attribute-value.created` | `createProductAttributeValuesWorkflow` creates values, including new values added to an inline axis | `{ id }` |
26
+ | `product-attribute-value.updated` | `updateProductAttributeValuesWorkflow` updates values | `{ id }` |
27
+ | `product-attribute-value.deleted` | `deleteProductAttributeValuesWorkflow` deletes values, including values removed from an inline axis | `{ id }` |
28
+
29
+ ## What doesn’t emit
30
+
31
+ Some writes create or change records without a matching event:
32
+
33
+ - **Values created with an attribute**: `createProductAttributesWorkflow` emits only `product-attribute.created`, not a value event per value
34
+ - **Upserts**: `upsertProductAttributeValuesWorkflow` emits nothing
35
+ - **Attaching and detaching**: linking values to a product, `text` and `unit` values created on attach, and option changes on a product emit no attribute events
36
+
37
+ ## Subscribe to an event
38
+
39
+ A subscriber receives one event per attribute. Resolve `query` to load the record by id.
40
+
41
+ ```ts title="src/subscribers/product-attribute-created.ts"
12
42
  import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
43
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
13
44
 
14
- export default async function attributeCreatedHandler({
45
+ export default async function productAttributeCreated({
15
46
  event,
16
47
  container,
17
48
  }: SubscriberArgs<{ id: string }>) {
18
- const attributeId = event.data.id
19
- // ...reindex filters, sync to an external system, etc.
49
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
50
+ const { data: [attribute] } = await query.graph({
51
+ entity: "product_attribute",
52
+ fields: ["id", "name", "type", "is_filterable", "values.name"],
53
+ filters: { id: event.data.id },
54
+ })
55
+
56
+ container.resolve("logger").info(`Index facet ${attribute.name}`)
20
57
  }
21
58
 
22
59
  export const config: SubscriberConfig = {
@@ -24,18 +61,4 @@ export const config: SubscriberConfig = {
24
61
  }
25
62
  ```
26
63
 
27
- ## Attribute events
28
-
29
- | Event | Emitted when | Payload |
30
- | --- | --- | --- |
31
- | `product-attribute.created` | An attribute is created | `{ id }` |
32
- | `product-attribute.updated` | An attribute changes | `{ id }` |
33
- | `product-attribute.deleted` | An attribute is deleted | `{ id }` |
34
-
35
- ## Value events
36
-
37
- | Event | Emitted when | Payload |
38
- | --- | --- | --- |
39
- | `product-attribute-value.created` | A value is created | `{ id }` |
40
- | `product-attribute-value.updated` | A value changes | `{ id }` |
41
- | `product-attribute-value.deleted` | A value is deleted | `{ id }` |
64
+ The subscriber loads the new attribute with its values. Swap the log line for a call to your search index.
@@ -1,45 +1,116 @@
1
1
  ---
2
- title: "Links to other modules"
2
+ title: "Attribute links"
3
3
  sidebarTitle: "Links"
4
- description: "How the Attribute (Product Attribute) domain links to products, categories, and options."
4
+ description: "The five module links between the Product Attribute module and Medusa's Product module."
5
5
  ---
6
6
 
7
- Modules in Mercur never reference each other directly. They're connected through
8
- **module links**. The Product Attribute module links into Medusa's product module
9
- in several ways: to products, to categories, and, for variant axes, to product
10
- options through mirror links. Once a link is defined, you retrieve related records
11
- with `query.graph` using the link alias.
7
+ The Product Attribute module never references Medusa’s Product module directly. Five module links in `packages/core/src/links` connect them: two that store data in pivot tables and three read-only links resolved from a foreign key on one side. Resolve `query` from the container to read across them.
12
8
 
13
9
  ```ts
14
- const { data: products } = await query.graph({
10
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
11
+
12
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
13
+ ```
14
+
15
+ ## Product to attribute value
16
+
17
+ The values a product holds, for every attribute type. This is the link the attachment workflows write.
18
+
19
+ | Property | Value |
20
+ | --- | --- |
21
+ | Sides | `Product` ↔ `ProductAttributeValue` |
22
+ | Cardinality | Many-to-many |
23
+ | Storage | Pivot table `product_attribute_value_link` |
24
+ | Aliases | `product_attribute_values` on product, `products` on value |
25
+ | Defined in | `product-attribute-value-pivot-link.ts` |
26
+
27
+ ```ts
28
+ const { data: [product] } = await query.graph({
15
29
  entity: "product",
16
- fields: ["id", "title", "scoped_attributes.*"],
30
+ fields: [
31
+ "product_attribute_values.name",
32
+ "product_attribute_values.attribute.name",
33
+ ],
34
+ filters: { id: "prod_01JD8Q1234567" },
17
35
  })
18
36
  ```
19
37
 
20
- ## Products
38
+ The query returns each linked value with its parent attribute, which is how you read a product’s full attribute selection.
21
39
 
22
- | Link | Table | Relationship |
23
- | --- | --- | --- |
24
- | **Product → attribute** (`scoped_attributes`) | None | Read-only; a product's inline (product-scoped) attributes, resolved from `ProductAttribute.product_id` |
25
- | **Product ↔ attribute value** | `product_attribute_value_link` | Many-to-many pivot; the values selected on a product |
40
+ ## Product to scoped attributes
26
41
 
27
- ## Categories
42
+ The inline attributes created on a product. The link is read-only and resolves `ProductAttribute.product_id` against `product.id`.
28
43
 
29
- | Link | Table | Relationship |
30
- | --- | --- | --- |
31
- | **Attribute ↔ category** (`categories`) | `product_category_attribute` | Many-to-many; the product categories an attribute is associated with |
44
+ | Property | Value |
45
+ | --- | --- |
46
+ | Sides | `Product` → `ProductAttribute` |
47
+ | Cardinality | One-to-many |
48
+ | Storage | None, read-only |
49
+ | Alias | `scoped_attributes` on product |
50
+ | Defined in | `product-attribute-product-link.ts` |
32
51
 
33
- ## Product options (mirror links)
52
+ ```ts
53
+ const { data: [product] } = await query.graph({
54
+ entity: "product",
55
+ fields: ["scoped_attributes.name", "scoped_attributes.values.name"],
56
+ filters: { id: "prod_01JD8Q1234567" },
57
+ })
58
+ ```
59
+
60
+ ## Attribute to category
34
61
 
35
- | Link | FK | Relationship |
36
- | --- | --- | --- |
37
- | **Attribute → option** | `ProductAttribute.product_option_id` | Read-only 1:1; the mirror `ProductOption` for a variant-axis attribute |
38
- | **Value → option value** | `ProductAttributeValue.product_option_value_id` | Read-only 1:1; the mirror `ProductOptionValue` for an axis value |
62
+ The product categories a global attribute is offered for. An attribute with no category links is offered for every product.
63
+
64
+ | Property | Value |
65
+ | --- | --- |
66
+ | Sides | `ProductAttribute` ↔ `ProductCategory` |
67
+ | Cardinality | Many-to-many |
68
+ | Storage | Pivot table `product_category_attribute` |
69
+ | Aliases | `categories` on attribute, `product_attributes` on category |
70
+ | Defined in | `product-attribute-category-link.ts` |
71
+
72
+ ```ts
73
+ const { data: attributes } = await query.graph({
74
+ entity: "product_attribute",
75
+ fields: ["id", "name", "categories.id", "categories.name"],
76
+ filters: { product_id: null },
77
+ })
78
+ ```
79
+
80
+ `createProductAttributesWorkflow` writes these links from `category_ids`. Write more yourself with the link service, using the keys `product_attribute_id` and `product_category_id`.
81
+
82
+ ## Attribute to product option
83
+
84
+ The Medusa `ProductOption` that backs a variant axis. The link is read-only and keyed on `ProductAttribute.product_option_id`.
85
+
86
+ | Property | Value |
87
+ | --- | --- |
88
+ | Sides | `ProductAttribute` → `ProductOption` |
89
+ | Cardinality | One-to-one |
90
+ | Storage | None, read-only |
91
+ | Defined in | `product-attribute-option-mirror-link.ts` |
92
+
93
+ ## Attribute value to option value
94
+
95
+ The Medusa `ProductOptionValue` that backs one value of a variant axis. The link is read-only and keyed on `ProductAttributeValue.product_option_value_id`.
96
+
97
+ | Property | Value |
98
+ | --- | --- |
99
+ | Sides | `ProductAttributeValue` → `ProductOptionValue` |
100
+ | Cardinality | One-to-one |
101
+ | Storage | None, read-only |
102
+ | Defined in | `product-attribute-value-option-value-mirror-link.ts` |
103
+
104
+ Core code reads the mirror through the foreign keys, not through these links. Do the same: read the ids from the attribute, then query the option.
105
+
106
+ ```ts
107
+ const { data: [option] } = await query.graph({
108
+ entity: "product_option",
109
+ fields: ["id", "title", "is_exclusive", "values.id", "values.value"],
110
+ filters: { id: attribute.product_option_id },
111
+ })
112
+ ```
39
113
 
40
114
  <Note>
41
- The mirror links (Attribute → option, Value → option value) and the product
42
- scope link (`scoped_attributes`) are **read-only**. They're resolved from the
43
- FK on the owning record and can't be written through the link itself. The
44
- workflows keep the mirror in sync when axis attributes and values change.
115
+ The workflows keep the mirror in step. Never write `product_option_id` or `product_option_value_id` yourself, or the attribute and its option will drift apart.
45
116
  </Note>
@@ -1,49 +1,88 @@
1
1
  ---
2
- title: "Service reference"
2
+ title: "Product Attribute module service"
3
3
  sidebarTitle: "Service"
4
- description: "The Product Attribute module service, with methods for working with records directly."
4
+ description: "Resolve the Product Attribute module service, and the generated and overridden methods it exposes."
5
5
  ---
6
6
 
7
- The Product Attribute module exposes a service you can resolve from the Medusa
8
- container to read and write records directly, without going through a workflow.
9
- Use it inside custom services, subscribers, or scheduled jobs.
7
+ The Product Attribute module service reads and writes `ProductAttribute` and `ProductAttributeValue` records directly. Use it for reads in routes, subscribers, and scheduled jobs. For writes, prefer the [workflows](/platform/attribute/reference/workflows), because the service doesn’t emit events, update the mirror `ProductOption`, or link values to products.
10
8
 
11
- ```ts
12
- import { MercurModules } from "@mercurjs/types"
9
+ ## Resolve the service
10
+
11
+ Resolve the service from the container with `MercurModules.PRODUCT_ATTRIBUTE`, whose value is `"product_attribute"`.
12
+
13
+ ```ts title="src/api/admin/catalog-import/facets/route.ts"
14
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
15
+ import { AttributeType, MercurModules } from "@mercurjs/types"
16
+
17
+ export async function GET(req: MedusaRequest, res: MedusaResponse) {
18
+ const attributeService = req.scope.resolve(MercurModules.PRODUCT_ATTRIBUTE)
13
19
 
14
- const attributeModuleService = container.resolve(
15
- MercurModules.PRODUCT_ATTRIBUTE,
16
- )
20
+ const [attributes, count] =
21
+ await attributeService.listAndCountProductAttributes(
22
+ { is_filterable: true, product_id: null },
23
+ { relations: ["values"], order: { rank: "ASC" } }
24
+ )
17
25
 
18
- const [attributes, count] =
19
- await attributeModuleService.listAndCountProductAttributes({
20
- type: "single_select",
21
- })
26
+ res.json({ attributes, count })
27
+ }
22
28
  ```
23
29
 
30
+ The route lists filterable global attributes in rank order, each with its values.
31
+
24
32
  ## Generated methods
25
33
 
26
- Each data model gets a standard set of auto-generated methods. For
27
- `ProductAttribute`:
34
+ The service extends `MedusaService`, so each data model gets the standard methods. For `ProductAttribute`:
28
35
 
29
36
  | Method | Description |
30
37
  | --- | --- |
31
- | `createProductAttributes(data)` | Create one or more attributes |
32
- | `retrieveProductAttribute(id, config?)` | Retrieve an attribute by id |
33
- | `listProductAttributes(filters?, config?)` | List attributes matching filters |
34
- | `listAndCountProductAttributes(filters?, config?)` | List attributes with a total count |
35
- | `updateProductAttributes(data)` | Update one or more attributes |
36
- | `deleteProductAttributes(ids)` | Delete one or more attributes |
37
-
38
- The same set exists for `ProductAttributeValue`: `createProductAttributeValues`,
39
- `retrieveProductAttributeValue`, `listProductAttributeValues`,
40
- `listAndCountProductAttributeValues`, `updateProductAttributeValues`, and
41
- `deleteProductAttributeValues`.
38
+ | `retrieveProductAttribute(id, config?)` | Retrieve one attribute by id |
39
+ | `listProductAttributes(filters?, config?)` | List attributes. Overridden, see below |
40
+ | `listAndCountProductAttributes(filters?, config?)` | List attributes with a total count. Overridden, see below |
41
+ | `createProductAttributes(data)` | Create one or more attributes. Overridden, see below |
42
+ | `updateProductAttributes(data)` | Update one or more attributes by id. Overridden, see below |
43
+ | `deleteProductAttributes(ids)` | Hard-delete attributes and their values |
44
+ | `softDeleteProductAttributes(ids)` | Soft-delete attributes and their values |
45
+ | `restoreProductAttributes(ids)` | Restore soft-deleted attributes |
46
+
47
+ `ProductAttributeValue` gets the same set: `retrieveProductAttributeValue`, `listProductAttributeValues`, `listAndCountProductAttributeValues`, `createProductAttributeValues`, `updateProductAttributeValues`, `deleteProductAttributeValues`, `softDeleteProductAttributeValues`, and `restoreProductAttributeValues`.
48
+
49
+ ## Overridden methods
50
+
51
+ Five methods add behavior on top of the generated ones.
52
+
53
+ ### createProductAttributes
54
+
55
+ ```ts
56
+ createProductAttributes(data: object | object[], sharedContext?: Context)
57
+ ```
58
+
59
+ Derives a `handle` from `name` for any global attribute created without one. After creating, it seeds a `"true"` value (rank 0) and a `"false"` value (rank 1) for every `toggle` attribute. It doesn’t create any other values, so add them with `createProductAttributeValues`.
60
+
61
+ ### updateProductAttributes
62
+
63
+ ```ts
64
+ updateProductAttributes(data: object | object[], sharedContext?: Context)
65
+ ```
66
+
67
+ Throws `INVALID_DATA` when an update sets a `type` that differs from the attribute’s current type. Every other field passes through.
68
+
69
+ ### listProductAttributes and listAndCountProductAttributes
70
+
71
+ ```ts
72
+ listProductAttributes(filters?, config?: FindConfig, sharedContext?: Context)
73
+ listAndCountProductAttributes(filters?, config?, sharedContext?)
74
+ ```
75
+
76
+ When `config.relations` or `config.select` asks for `values`, the service loads them in a separate query. Only `single_select`, `multi_select`, and `toggle` attributes get their values. `text` and `unit` attributes come back with `values: []`, because their values belong to individual products.
77
+
78
+ ### createProductAttributeValues
79
+
80
+ ```ts
81
+ createProductAttributeValues(data: object | object[], sharedContext?: Context)
82
+ ```
83
+
84
+ Derives a `handle` from `name` for values of global `single_select` and `multi_select` attributes. Values of inline attributes and of `text`, `unit`, and `toggle` attributes keep a `null` handle.
42
85
 
43
86
  <Warning>
44
- Prefer [workflows](/platform/attribute/reference/workflows) for anything with
45
- side effects, such as creating an axis attribute, attaching attributes to a
46
- product, or keeping the mirror `ProductOption` in sync. The service writes records
47
- directly and does **not** emit events, maintain the option mirror, or run
48
- compensation.
87
+ Writing `product_option_id` or `product_option_value_id` through the service doesn’t create or change any Medusa option. Use `createProductAttributesWorkflow` and the value workflows so the mirror stays correct.
49
88
  </Warning>
@@ -1,45 +1,175 @@
1
1
  ---
2
- title: "Workflows"
2
+ title: "Attribute workflows"
3
3
  sidebarTitle: "Workflows"
4
- description: "Attribute catalog and product-attachment workflows."
4
+ description: "Every catalog and product attachment workflow in the Product Attribute domain, with inputs, hooks, and events."
5
5
  ---
6
6
 
7
- This reference lists the workflows for the Attribute domain. Import them from
8
- `@mercurjs/core/workflows` and run them against the Medusa container.
7
+ The Product Attribute domain exports 11 workflows from `@mercurjs/core/workflows`: 7 that manage the catalog and 4 that attach attributes to products. Run each one with `workflow(container).run({ input })`. When a step fails, the steps that already ran undo their work through their compensation functions.
9
8
 
10
9
  ## Catalog workflows
11
10
 
12
- Manage the attribute catalog and its values.
11
+ These workflows create, change, and delete attributes and values. They keep the mirror `ProductOption` of a variant axis in step.
13
12
 
14
- | Workflow | Input | Purpose |
13
+ ### createProductAttributesWorkflow
14
+
15
+ Creates attributes and their values. For each `multi_select` attribute with `is_variant_axis`, it first creates a global `ProductOption` (`is_exclusive: false`) titled with the attribute name, then links the values to its option values. Id `create-product-attributes`.
16
+
17
+ | Input | Type | Description |
18
+ | --- | --- | --- |
19
+ | `attributes` | `(CreateProductAttributeDTO & { category_ids?: string[] })[]` | Attributes to create, each with optional `values` |
20
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesCreated` hook |
21
+
22
+ - **Returns**: `ProductAttributeDTO[]`
23
+ - **Hooks**: `validate` with `{ input }`, `productAttributesCreated` with `{ attributes, additional_data }`
24
+ - **Events**: `product-attribute.created` per attribute
25
+
26
+ ### updateProductAttributesWorkflow
27
+
28
+ Updates every attribute matching `selector`. When `update.name` is set, it also renames the mirror option of each axis attribute. Id `update-product-attributes`.
29
+
30
+ | Input | Type | Description |
31
+ | --- | --- | --- |
32
+ | `selector` | `Record<string, unknown>` | Filters for the attributes to update, such as `{ id }` |
33
+ | `update` | `UpdateProductAttributeDTO` | Fields to set. Changing `type` throws |
34
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesUpdated` hook |
35
+
36
+ - **Returns**: `ProductAttributeDTO[]`
37
+ - **Hooks**: `validate` with `{ input }`, `productAttributesUpdated` with `{ attributes, additional_data }`
38
+ - **Events**: `product-attribute.updated` per attribute
39
+
40
+ ### deleteProductAttributesWorkflow
41
+
42
+ Soft-deletes attributes and their values, then deletes the mirror options of axis attributes. It throws `NOT_ALLOWED` if any value of an attribute is linked to a product. Id `delete-product-attributes`.
43
+
44
+ | Input | Type | Description |
45
+ | --- | --- | --- |
46
+ | `ids` | `string[]` | Attribute ids |
47
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesDeleted` hook |
48
+
49
+ - **Returns**: nothing
50
+ - **Hooks**: `validate` with `{ input }`, `productAttributesDeleted` with `{ ids, additional_data }`
51
+ - **Events**: `product-attribute.deleted` per id
52
+
53
+ ### createProductAttributeValuesWorkflow
54
+
55
+ Adds values to one attribute. For an axis, it first adds the names to the mirror option and stores each new option value’s id. Id `create-product-attribute-values`.
56
+
57
+ | Input | Type | Description |
58
+ | --- | --- | --- |
59
+ | `attribute_id` | `string` | The parent attribute |
60
+ | `values` | `CreateProductAttributeValueDTO[]` | Values to create, each with at least a `name` |
61
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributeValuesCreated` hook |
62
+
63
+ - **Returns**: `ProductAttributeValueDTO[]`
64
+ - **Hooks**: `validate` with `{ input }`, `productAttributeValuesCreated` with `{ values, additional_data }`
65
+ - **Events**: `product-attribute-value.created` per value
66
+
67
+ ### updateProductAttributeValuesWorkflow
68
+
69
+ Updates every value matching `selector`. When the values belong to exactly one axis attribute, it rewrites that option’s values from the attribute’s value names. Id `update-product-attribute-values`.
70
+
71
+ | Input | Type | Description |
72
+ | --- | --- | --- |
73
+ | `selector` | `Record<string, unknown>` | Filters for the values to update |
74
+ | `update` | `UpdateProductAttributeValueDTO` | Fields to set |
75
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributeValuesUpdated` hook |
76
+
77
+ - **Returns**: `ProductAttributeValueDTO[]`
78
+ - **Hooks**: `validate` with `{ input }`, `productAttributeValuesUpdated` with `{ values, additional_data }`
79
+ - **Events**: `product-attribute-value.updated` per value
80
+
81
+ ### deleteProductAttributeValuesWorkflow
82
+
83
+ Soft-deletes values. When they belong to exactly one axis attribute, it rewrites that option’s values from the remaining value names. Id `delete-product-attribute-values`.
84
+
85
+ | Input | Type | Description |
86
+ | --- | --- | --- |
87
+ | `ids` | `string[]` | Value ids |
88
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributeValuesDeleted` hook |
89
+
90
+ - **Returns**: nothing
91
+ - **Hooks**: `validate` with `{ input }`, `productAttributeValuesDeleted` with `{ ids, additional_data }`
92
+ - **Events**: `product-attribute-value.deleted` per id
93
+
94
+ ### upsertProductAttributeValuesWorkflow
95
+
96
+ Creates values without an `id` and updates values with one, for a single attribute. For an axis, it adds any new names to the mirror option first. Id `upsert-product-attribute-values`.
97
+
98
+ | Input | Type | Description |
99
+ | --- | --- | --- |
100
+ | `attribute_id` | `string` | The parent attribute |
101
+ | `values` | `UpsertProductAttributeValueDTO[]` | Values to create or update, matched by `id` |
102
+
103
+ - **Returns**: `ProductAttributeValueDTO[]`, created values first
104
+ - **Hooks**: none
105
+ - **Events**: none
106
+
107
+ ## Product attachment workflows
108
+
109
+ These workflows attach attributes to products, write the `product_attribute_value_link` pivot, and attach or detach axis options on the product. Their input types are `ProductAttributeBatchAdd` and `ProductAttributeBatchUpdate` from `@mercurjs/types`.
110
+
111
+ ### createAndLinkProductAttributesToProductWorkflow
112
+
113
+ The batch engine. It runs the remove, add, and update workflows below in that order, skipping any list that’s empty. Id `create-and-link-product-attributes-to-product`.
114
+
115
+ | Input | Type | Description |
15
116
  | --- | --- | --- |
16
- | `createProductAttributesWorkflow` | `{ attributes[], additional_data? }` | Create attributes (+ values, + mirror option for axes) |
17
- | `updateProductAttributesWorkflow` | `{ selector, update }` | Update attribute fields |
18
- | `deleteProductAttributesWorkflow` | `{ ids[] }` | Delete attributes (fails if still linked) |
19
- | `createProductAttributeValuesWorkflow` | `{ values[], additional_data? }` | Add values to attributes |
20
- | `updateProductAttributeValuesWorkflow` | `{ selector, update }` | Update values |
21
- | `deleteProductAttributeValuesWorkflow` | `{ ids[] }` | Delete values |
22
- | `upsertProductAttributeValuesWorkflow` | `{ attribute_id, values[] }` | Create or update an attribute's values in one call |
117
+ | `product_id` | `string` | The product to change |
118
+ | `add` | `ProductAttributeBatchAdd[]` | Attributes to attach, existing or inline |
119
+ | `remove` | `string[]` | Attribute ids to detach |
120
+ | `update` | `ProductAttributeBatchUpdate[]` | Selections to change |
121
+ | `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesLinked` hook |
23
122
 
24
- ## Product-attachment workflows
123
+ - **Returns**: nothing
124
+ - **Hooks**: `validate` with `{ input }`, `productAttributesLinked` with `{ product_id, additional_data }`
125
+ - **Events**: none of its own. Nested catalog workflows emit theirs
25
126
 
26
- Attach attributes to products in the shared master catalog.
127
+ ### addProductAttributesToProductWorkflow
27
128
 
28
- | Workflow | Input | Purpose |
129
+ Attaches attributes. It creates inline attributes and their exclusive options, creates `text` and `unit` values, attaches axis options with the selected subset, and links every value to the product. Id `add-product-attributes-to-product`.
130
+
131
+ | Input | Type | Description |
29
132
  | --- | --- | --- |
30
- | `createAndLinkProductAttributesToProductWorkflow` | `{ product_id, add?, remove?, update? }` | Batch engine: attach, detach, and update, applied remove → add → update |
31
- | `addProductAttributesToProductWorkflow` | `{ product_id, add[] }` | Attach attributes (existing or inline) to a product |
32
- | `removeProductAttributesFromProductWorkflow` | `{ product_id, ... }` | Detach attributes from a product |
33
- | `updateProductAttributesOnProductWorkflow` | `{ product_id, ... }` | Update a product's attribute selections |
133
+ | `product_id` | `string` | The product to change |
134
+ | `add` | `ProductAttributeBatchAdd[]` | Attributes to attach |
135
+
136
+ The input can also be an array of `{ product_id, add }` objects to attach to several products in one run. It has no hooks and emits no events.
137
+
138
+ ### removeProductAttributesFromProductWorkflow
139
+
140
+ Detaches attributes. It throws `NOT_ALLOWED` for a required attribute unless the id is also in `readd`. It then detaches global axis options, drops the value links, and deletes inline attributes through `deleteProductAttributesWorkflow`. Id `remove-product-attributes-from-product`.
141
+
142
+ | Input | Type | Description |
143
+ | --- | --- | --- |
144
+ | `product_id` | `string` | The product to change |
145
+ | `remove` | `string[]` | Attribute ids to detach |
146
+ | `readd` | `string[]` | Ids removed and re-added in the same batch, skipped by the required check |
147
+
148
+ ### updateProductAttributesOnProductWorkflow
149
+
150
+ Changes selections. It resolves `value_ids` into an add and remove delta, moves global axis option values, swaps `text`, `unit`, and `toggle` values, adds or deletes values of an inline axis, and renames inline attributes from `title`. Id `update-product-attributes-on-product`.
151
+
152
+ | Input | Type | Description |
153
+ | --- | --- | --- |
154
+ | `product_id` | `string` | The product to change |
155
+ | `update` | `ProductAttributeBatchUpdate[]` | Selections to change |
34
156
 
35
157
  <Note>
36
- `createAndLinkProductAttributesToProductWorkflow` is the engine behind the
37
- product attribute batch endpoint; it composes the three single-purpose
38
- attachment workflows. See
39
- [Attach attributes to a product](/platform/attribute/guides/attach-attributes-to-a-product).
158
+ Changes made by these workflows land on the product right away. To route a store’s attribute edits through review, run `productEditUpdateAttributesWorkflow` from the [Product edits](/platform/product-edit/overview) domain, which applies the batch engine once the change is confirmed.
40
159
  </Note>
41
160
 
42
- To work with records directly instead of through a workflow, see the
43
- [Service reference](/platform/attribute/reference/service). To run side effects
44
- when an attribute changes, see the
45
- [Event reference](/platform/attribute/reference/events).
161
+ ## Steps
162
+
163
+ The domain also exports its steps for use in your own workflows:
164
+
165
+ | Step | Id | Compensation |
166
+ | --- | --- | --- |
167
+ | `createProductAttributesStep` | `pa-create-product-attributes` | Deletes the created attributes |
168
+ | `updateProductAttributesStep` | `pa-update-product-attributes` | Restores previous field values |
169
+ | `deleteProductAttributesStep` | `pa-delete-product-attributes` | Restores the soft-deleted attributes |
170
+ | `createProductAttributeValuesStep` | `pa-create-product-attribute-values` | Deletes the created values |
171
+ | `updateProductAttributeValuesStep` | `pa-update-product-attribute-values` | Restores previous field values |
172
+ | `deleteProductAttributeValuesStep` | `pa-delete-product-attribute-values` | Restores the soft-deleted values |
173
+ | `detachProductOptionValuesFromProductStep` | `pa-detach-product-option-values-from-product` | Re-attaches the option values |
174
+ | `validateProductAttributesNotLinkedStep` | `pa-validate-attributes-not-linked` | None |
175
+ | `validateProductAttributesNotRequiredStep` | `pa-validate-attributes-not-required` | None |