@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,83 +1,132 @@
1
1
  ---
2
- title: "Create a variant axis"
2
+ title: "Create a variant axis and build variants on it"
3
3
  sidebarTitle: "Create a variant axis"
4
- description: "Generate product variants from a multi_select axis attribute."
4
+ description: "Create a multi_select axis attribute, then create a product whose variants bind to it."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to create a variant-axis attribute and use it to
8
- generate a product's variants from server code.
9
-
10
- A variant axis is a `multi_select` attribute with `is_variant_axis` set. Creating
11
- one mirrors a native Medusa `ProductOption`, so the values a product selects along
12
- the axis become the dimensions Medusa uses to generate variants.
13
-
14
- ## Create the axis attribute
15
-
16
- ```ts title="src/api/custom/attributes/route.ts"
17
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
18
- import { createProductAttributesWorkflow } from "@mercurjs/core/workflows"
19
-
20
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
21
- const { result } = await createProductAttributesWorkflow(req.scope).run({
22
- input: {
23
- attributes: [
24
- {
25
- name: "Size",
26
- type: "multi_select",
27
- is_variant_axis: true,
28
- is_filterable: true,
29
- values: [{ name: "S" }, { name: "M" }, { name: "L" }],
30
- },
31
- ],
32
- },
33
- })
7
+ Create a variant axis such as Size, then create a product that uses part of it and has one variant per selected value. You need this whenever products in your catalog come in sizes, colors, or any other dimension shoppers pick between.
34
8
 
35
- res.status(201).json({ attribute: result[0] })
36
- }
37
- ```
9
+ ## Before you begin
38
10
 
39
- The workflow creates the `ProductAttribute`, a mirror `ProductOption`, and a
40
- `ProductAttributeValue` for each option value. It wires `product_option_id` and
41
- `product_option_value_id` behind the scenes.
11
+ You need the following:
42
12
 
43
- ## Attach it to a product
13
+ - **A Mercur server**: an API app with `@mercurjs/core` registered through `withMercur()`
14
+ - **The concept**: read [Variant axes and product options](/platform/attribute/concepts/variant-axes) to see how an axis maps to a Medusa `ProductOption`
44
15
 
45
- Attach the axis to a product and pass the subset of values that product offers.
46
- Medusa generates a variant for each selected value.
16
+ ## Build the axis and the product
47
17
 
48
- ```ts
49
- import { createAndLinkProductAttributesToProductWorkflow } from "@mercurjs/core/workflows"
18
+ <Steps>
19
+ <Step title="Create the axis attribute">
20
+ An axis is a `multi_select` attribute with `is_variant_axis: true`. The workflow creates a global `ProductOption` titled `Size` with the same three values.
21
+
22
+ ```ts title="src/scripts/seed-size-axis.ts"
23
+ import type { ExecArgs } from "@medusajs/framework/types"
24
+ import { AttributeType } from "@mercurjs/types"
25
+ import { createProductAttributesWorkflow } from "@mercurjs/core/workflows"
50
26
 
27
+ export default async function seedSizeAxis({ container }: ExecArgs) {
28
+ const { result } = await createProductAttributesWorkflow(container).run({
29
+ input: {
30
+ attributes: [
31
+ {
32
+ name: "Size",
33
+ type: AttributeType.MULTI_SELECT,
34
+ is_variant_axis: true,
35
+ is_filterable: true,
36
+ values: [{ name: "S" }, { name: "M" }, { name: "L" }],
37
+ },
38
+ ],
39
+ },
40
+ })
41
+
42
+ container.resolve("logger").info(`Created axis ${result[0].id}`)
43
+ }
44
+ ```
45
+
46
+ Run it with `npx medusa exec ./src/scripts/seed-size-axis.ts`. Medusa keeps global option titles unique, so a second global axis named `Size` fails.
47
+ </Step>
48
+ <Step title="Create a product on the axis">
49
+ Mercur’s `createProductsWorkflow` takes `attributes` and `variants` together. It attaches the axis with the values you pick, then creates the variants against it.
50
+
51
+ ```ts
52
+ import { createProductsWorkflow } from "@mercurjs/core/workflows"
53
+
54
+ const { result } = await createProductsWorkflow(req.scope).run({
55
+ input: {
56
+ created_by: req.auth_context.actor_id,
57
+ products: [
58
+ {
59
+ title: "Linen shirt",
60
+ attributes: [
61
+ {
62
+ id: "pattr_01JD8Q1234567",
63
+ value_ids: ["pattrval_01JD8Q1", "pattrval_01JD8Q2"],
64
+ },
65
+ ],
66
+ variants: [
67
+ { title: "Linen shirt / S", options: { Size: "S" } },
68
+ { title: "Linen shirt / M", options: { Size: "M" } },
69
+ ],
70
+ },
71
+ ],
72
+ },
73
+ })
74
+ ```
75
+
76
+ Run this inside an authenticated route handler, so `req.auth_context.actor_id` records who created the product. Each variant names its option value by the option title, which is the attribute name. The product only holds `S` and `M`, so those are the variants it can have.
77
+ </Step>
78
+ </Steps>
79
+
80
+ ## Change the values a product uses
81
+
82
+ Run `createAndLinkProductAttributesToProductWorkflow` with an `update` entry to grow or shrink the subset. The workflow moves the option values on the product and the pivot links together.
83
+
84
+ ```ts
51
85
  await createAndLinkProductAttributesToProductWorkflow(req.scope).run({
52
86
  input: {
53
- product_id: "prod_shirt",
54
- add: [{ id: "pattr_size", value_ids: ["pattrval_s", "pattrval_m"] }],
87
+ product_id: "prod_01JD8Q1234567",
88
+ update: [
89
+ {
90
+ id: "pattr_01JD8Q1234567",
91
+ add: ["pattrval_01JD8Q3"],
92
+ remove: ["pattrval_01JD8Q1"],
93
+ },
94
+ ],
55
95
  },
56
96
  })
57
97
  ```
58
98
 
59
- <Note>
60
- Only `multi_select` attributes can be variant axes. The `value_ids` you pass
61
- are the per-product subset of the axis's values. Only those become variants.
62
- </Note>
99
+ After the run, the product offers `M` and `L`. Create the `L` variant with Medusa’s `createProductVariantsWorkflow` and `options: { Size: "L" }`.
63
100
 
64
- ## Inline axes
101
+ ## Create an axis for one product
65
102
 
66
- To create a product-scoped axis in the same step it's attached, pass the inline
67
- form instead of an existing id. This creates an exclusive `ProductOption`, a
68
- scoped attribute (`product_id` set), and the value mirror in one call:
103
+ When only one product needs a dimension, add an inline axis instead of a global one. It creates an exclusive option (`is_exclusive: true`), a product-scoped `multi_select` attribute, and its values, and attaches them to the product.
69
104
 
70
105
  ```ts
71
106
  await createAndLinkProductAttributesToProductWorkflow(req.scope).run({
72
107
  input: {
73
- product_id: "prod_shirt",
74
- add: [{ title: "Cut", is_variant_axis: true, values: ["Slim", "Regular"] }],
108
+ product_id: "prod_01JD8Q1234567",
109
+ add: [
110
+ { title: "Cut", is_variant_axis: true, values: ["Slim", "Regular"] },
111
+ ],
75
112
  },
76
113
  })
77
114
  ```
78
115
 
79
- <Tip>
80
- Inline axes are ideal for a one-off dimension a single product needs. Reach for
81
- a global axis attribute when the same dimension, such as Size or Color, recurs
82
- across the catalog. See [Global vs inline](/platform/attribute/concepts/global-vs-inline).
83
- </Tip>
116
+ An inline axis holds all of its values, so there’s no subset to pick. You add values to it later with `update` and `add: [{ value: "Relaxed" }]`.
117
+
118
+ ## Add values to a global axis
119
+
120
+ `createProductAttributeValuesWorkflow` adds values to the attribute and to its global option in one run. Products don’t gain the new value until you add it to their subset.
121
+
122
+ ```ts
123
+ await createProductAttributeValuesWorkflow(req.scope).run({
124
+ input: { attribute_id: "pattr_01JD8Q1234567", values: [{ name: "XL" }] },
125
+ })
126
+ ```
127
+
128
+ ## Next steps
129
+
130
+ - [Every add and update form for the batch workflow](/platform/attribute/guides/attach-attributes-to-a-product)
131
+ - [Global and inline attributes](/platform/attribute/concepts/global-vs-inline)
132
+ - [The workflows that keep the option in sync](/platform/attribute/reference/workflows)
@@ -1,81 +1,130 @@
1
1
  ---
2
- title: "Create an attribute"
2
+ title: "Create a catalog attribute"
3
3
  sidebarTitle: "Create an attribute"
4
- description: "Create a typed catalog attribute with createProductAttributesWorkflow."
4
+ description: "Create a global attribute with its values from a route, a seed script, or a catalog import."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to create a global catalog attribute from your
8
- own server code, for example in a seed script, a custom API route, or a catalog
9
- import.
7
+ Create global catalog attributes from your own server code, for example when you seed a marketplace or import a taxonomy from a product information system.
10
8
 
11
- Mercur exposes a `createProductAttributesWorkflow` that creates one or more
12
- `ProductAttribute` records along with their values. Run it from any place that
13
- has access to the Medusa container.
9
+ ## Before you begin
14
10
 
15
- ## Run the workflow
11
+ You need the following:
16
12
 
17
- ```ts title="src/api/custom/attributes/route.ts"
18
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
19
- import { createProductAttributesWorkflow } from "@mercurjs/core/workflows"
13
+ - **A Mercur server**: an API app with `@mercurjs/core` registered through `withMercur()`
14
+ - **Workflow imports**: every workflow on this page comes from `@mercurjs/core/workflows`
15
+
16
+ ## Create the attribute
17
+
18
+ <Steps>
19
+ <Step title="Run createProductAttributesWorkflow from a route">
20
+ The workflow takes a list of attributes, each with its values, and returns the created records.
20
21
 
21
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
22
- const { result } = await createProductAttributesWorkflow(req.scope).run({
23
- input: {
24
- attributes: [
25
- {
26
- name: "Material",
27
- type: "single_select",
28
- is_filterable: true,
29
- values: [{ name: "Cotton" }, { name: "Wool" }, { name: "Linen" }],
22
+ ```ts title="src/api/admin/catalog-import/attributes/route.ts"
23
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
24
+ import { AttributeType } from "@mercurjs/types"
25
+ import { createProductAttributesWorkflow } from "@mercurjs/core/workflows"
26
+
27
+ export async function POST(req: MedusaRequest, res: MedusaResponse) {
28
+ const { result } = await createProductAttributesWorkflow(req.scope).run({
29
+ input: {
30
+ attributes: [
31
+ {
32
+ name: "Material",
33
+ type: AttributeType.SINGLE_SELECT,
34
+ is_filterable: true,
35
+ values: [{ name: "Cotton" }, { name: "Linen" }],
36
+ },
37
+ ],
30
38
  },
31
- ],
32
- },
33
- })
39
+ })
34
40
 
35
- res.status(201).json({ attribute: result[0] })
36
- }
37
- ```
41
+ res.status(201).json({ product_attributes: result })
42
+ }
43
+ ```
38
44
 
39
- The workflow creates the attribute, its `ProductAttributeValue` records, and,
40
- for a `multi_select` axis, the mirror `ProductOption`. It also emits the
41
- `product-attribute.created` event.
45
+ Leaving out `product_id` makes the attribute global. The service derives the handle `material` and a handle for each value.
46
+ </Step>
47
+ <Step title="Scope it to categories">
48
+ Pass `category_ids` to link the attribute to product categories in the same run. An attribute with no categories is offered for every product.
42
49
 
43
- <Note>
44
- Leaving `product_id` unset creates a **global** attribute reusable across the
45
- catalog. Passing a `product_id` creates an **inline** attribute scoped to a
46
- single product. See [Global vs inline](/platform/attribute/concepts/global-vs-inline).
47
- </Note>
50
+ ```ts
51
+ await createProductAttributesWorkflow(req.scope).run({
52
+ input: {
53
+ attributes: [
54
+ {
55
+ name: "Thread count",
56
+ type: AttributeType.UNIT,
57
+ category_ids: ["pcat_01JD8Q1234567"],
58
+ },
59
+ ],
60
+ },
61
+ })
62
+ ```
48
63
 
49
- ## Associate categories
64
+ A `unit` attribute has no predefined values. Each product gets its own value when you attach the attribute.
65
+ </Step>
66
+ <Step title="Add values later">
67
+ Run `createProductAttributeValuesWorkflow` to add values to an existing select attribute. It takes one `attribute_id` and the new values.
50
68
 
51
- Pass `category_ids` on an attribute to associate it with product categories
52
- through the category link in the same call:
69
+ ```ts
70
+ import { createProductAttributeValuesWorkflow } from "@mercurjs/core/workflows"
53
71
 
54
- ```ts
55
- await createProductAttributesWorkflow(req.scope).run({
56
- input: {
57
- attributes: [
58
- {
59
- name: "Thread count",
60
- type: "unit",
61
- category_ids: ["pcat_bedding"],
72
+ await createProductAttributeValuesWorkflow(req.scope).run({
73
+ input: {
74
+ attribute_id: "pattr_01JD8Q1234567",
75
+ values: [{ name: "Wool" }, { name: "Silk", rank: 3 }],
62
76
  },
63
- ],
64
- },
65
- })
77
+ })
78
+ ```
79
+
80
+ For a variant axis, the workflow also adds the values to the mirror `ProductOption`.
81
+ </Step>
82
+ </Steps>
83
+
84
+ ## Attach custom data with additional_data
85
+
86
+ Every catalog workflow accepts `additional_data` and passes it to its completion hook. Register a handler on `productAttributesCreated` to store your own data next to the attribute without forking the workflow.
87
+
88
+ ```ts title="src/workflows/hooks/product-attributes-created.ts"
89
+ import { createProductAttributesWorkflow } from "@mercurjs/core/workflows"
90
+
91
+ createProductAttributesWorkflow.hooks.productAttributesCreated(
92
+ async ({ attributes, additional_data }, { container }) => {
93
+ const logger = container.resolve("logger")
94
+ const source = additional_data?.imported_from
95
+
96
+ for (const attribute of attributes) {
97
+ logger.info(`Attribute ${attribute.id} imported from ${source}`)
98
+ }
99
+ }
100
+ )
66
101
  ```
67
102
 
68
- ## Attach custom data
103
+ Pass the data when you run the workflow, for example `additional_data: { imported_from: "legacy_pim" }`. The workflow also exposes a `validate` hook that runs before anything is written, so you can throw there to reject the input.
104
+
105
+ ## Update or delete an attribute
69
106
 
70
- The workflow accepts an `additional_data` payload passed to its
71
- `productAttributesCreated` hook, letting you persist marketplace-specific data
72
- alongside the attribute without forking the workflow.
107
+ `updateProductAttributesWorkflow` takes a `selector` and an `update`. Keep these rules in mind:
108
+
109
+ - **Type is fixed**: the service throws if `update.type` differs from the current type
110
+ - **Renames reach the option**: changing `name` on an axis also renames its `ProductOption`
111
+ - **Deletes check usage**: `deleteProductAttributesWorkflow` throws `NOT_ALLOWED` while any value is linked to a product
73
112
 
74
113
  ```ts
75
- await createProductAttributesWorkflow(req.scope).run({
114
+ import { updateProductAttributesWorkflow } from "@mercurjs/core/workflows"
115
+
116
+ await updateProductAttributesWorkflow(req.scope).run({
76
117
  input: {
77
- attributes: [{ name: "Material", type: "single_select" }],
78
- additional_data: { imported_from: "legacy-pim" },
118
+ selector: { id: "pattr_01JD8Q1234567" },
119
+ update: { name: "Fabric", is_filterable: false },
79
120
  },
80
121
  })
81
122
  ```
123
+
124
+ The run renames the attribute and turns off its facet flag. The handle stays `material`, because handles are derived only on create.
125
+
126
+ ## Next steps
127
+
128
+ - [Attach the attribute to a product](/platform/attribute/guides/attach-attributes-to-a-product)
129
+ - [Create a variant axis](/platform/attribute/guides/create-a-variant-axis)
130
+ - [Subscribe to attribute events](/platform/attribute/reference/events)
@@ -1,82 +1,86 @@
1
1
  ---
2
- title: "Attribute"
2
+ title: "Attributes"
3
3
  sidebarTitle: "Overview"
4
- description: "An operator-managed, typed attribute catalog for describing and filtering the shared product catalog."
4
+ description: "Describe the shared catalog with typed attributes, turn them into variant axes, and expose them as storefront filters."
5
5
  ---
6
6
 
7
- Use Mercur to define a typed vocabulary for your products, such as colors,
8
- materials, sizes, capacities, or warranty toggles, and reuse it across the whole
9
- marketplace.
7
+ Use attributes to give every product in your marketplace the same typed vocabulary, from sizes and materials to capacities and yes/no flags.
10
8
 
11
- The Attribute domain is an operator-managed catalog of typed fields that attach
12
- to products in the shared master catalog. Attributes describe products
13
- consistently and drive storefront filters. For variant axes, they generate the
14
- product variants shoppers pick between. Every attribute is one of five types.
15
- Each one is either a global catalog entry reused everywhere or an inline field
16
- scoped to a single product.
9
+ An attribute is a typed field that operators define once and attach to products in the shared master catalog. It describes products the same way across every store, feeds storefront filters, and, when it’s a variant axis, decides which variants a product can have. The Product Attribute module holds the attributes and their values. Links connect them to Medusa’s products, categories, and product options, so a variant axis is a real Medusa `ProductOption` underneath.
10
+
11
+ <img className="block dark:hidden" src="/images/figures/attribute-overview-light.svg" alt="The global attribute catalog lists Size, Material, Thread count, Gift wrap, and Care with their types, and Size is the one variant axis, backed by the global Medusa product option Size behind it." />
12
+ <img className="hidden dark:block" src="/images/figures/attribute-overview-dark.svg" alt="The global attribute catalog lists Size, Material, Thread count, Gift wrap, and Care with their types, and Size is the one variant axis, backed by the global Medusa product option Size behind it." />
17
13
 
18
14
  <Note>
19
- **Attribute = the `ProductAttribute` entity.** The catalog is owned by the
20
- Product Attribute module (`MercurModules.PRODUCT_ATTRIBUTE`, id prefix
21
- `pattr`). Products are the shared master catalog. Attributes never belong to a
22
- store. Sellers list against master products through offers.
15
+ The data models are `ProductAttribute` (id prefix `pattr`) and `ProductAttributeValue` (id prefix `pattrval`), resolved with `MercurModules.PRODUCT_ATTRIBUTE` from `@mercurjs/types`. Attributes belong to the catalog, never to a store.
23
16
  </Note>
24
17
 
25
18
  ## Key features
26
19
 
27
- - **Five typed forms:** `single_select`, `multi_select`, `text`, `unit`, and `toggle`, each with its own validation and UI shape.
28
- - **Variant axes:** a `multi_select` attribute marked `is_variant_axis` mirrors a native Medusa `ProductOption` and generates product variants.
29
- - **Global or inline:** reuse a global catalog attribute across products, or attach a one-off attribute scoped to a single product.
30
- - **Storefront filtering:** an `is_filterable` flag exposes an attribute as a shopper-facing facet.
31
- - **Batch attach:** attach, detach, and update all of a product's attributes through a single engine workflow.
32
- - **Ordered and governed:** `rank`, `is_active`, and `is_required` control ordering, availability, and required-field enforcement.
20
+ The Product Attribute module covers the full life of a catalog attribute:
21
+
22
+ - **Five types**: `single_select`, `multi_select`, `text`, `unit`, and `toggle`, each storing its values differently
23
+ - **Variant axes**: a `multi_select` attribute with `is_variant_axis` creates a Medusa `ProductOption` that variants bind to
24
+ - **Global or inline**: define an attribute once for the whole catalog, or create a one-off attribute on a single product
25
+ - **Category scoping**: link an attribute to product categories so it’s offered only for products in them
26
+ - **Storefront filters**: shoppers filter products by axis values, and `is_filterable` tells your storefront which attributes to show as facets
27
+ - **One batch workflow**: attach, detach, and update all of a product’s attributes in a single run
28
+ - **Hooks and events**: catalog workflows expose hooks and emit `product-attribute.*` and `product-attribute-value.*` events
29
+
30
+ ## How it works
31
+
32
+ An attribute reaches a product in four steps:
33
+
34
+ 1. An operator runs `createProductAttributesWorkflow`. For a variant axis, it also creates a global `ProductOption` with the same values.
35
+ 2. A product is created with Mercur’s `createProductsWorkflow`, or edited with `createAndLinkProductAttributesToProductWorkflow`, and picks the attributes and values it uses.
36
+ 3. Selected values link to the product. For an axis, the product also gets the option with only the selected subset of values.
37
+ 4. Variants bind to the axis option by its title, for example `{ Size: "M" }`, and the storefront filters products by those option values.
33
38
 
34
39
  ## Get started
35
40
 
36
- Learn how the domain fits together.
41
+ Learn how attributes, values, and axes fit together.
37
42
 
38
43
  <CardGroup cols={2}>
39
44
  <Card title="Attribute types" icon="shapes" href="/platform/attribute/concepts/attribute-types">
40
- The five attribute types and the values they hold.
45
+ The five types and how each one stores its values.
41
46
  </Card>
42
47
  <Card title="Variant axes" icon="sitemap" href="/platform/attribute/concepts/variant-axes">
43
- How `is_variant_axis` mirrors a `ProductOption` and generates variants.
48
+ How an axis attribute mirrors a Medusa `ProductOption`.
44
49
  </Card>
45
50
  <Card title="Global vs inline" icon="layer-group" href="/platform/attribute/concepts/global-vs-inline">
46
- Catalog-wide attributes, product-scoped attributes, and filtering.
51
+ Catalog attributes, product-scoped attributes, categories, and filters.
47
52
  </Card>
48
53
  </CardGroup>
49
54
 
50
55
  ## Examples
51
56
 
52
- Build against the Attribute domain in your own code.
57
+ Build against attributes in your own server code.
53
58
 
54
59
  <CardGroup cols={2}>
55
60
  <Card title="Create an attribute" icon="plus" href="/platform/attribute/guides/create-an-attribute">
56
61
  Run `createProductAttributesWorkflow` from a route or seed script.
57
62
  </Card>
58
63
  <Card title="Attach to a product" icon="link" href="/platform/attribute/guides/attach-attributes-to-a-product">
59
- Attach, detach, and update attributes in one batch call.
64
+ Attach, detach, and update a product’s attributes in one run.
60
65
  </Card>
61
66
  <Card title="Create a variant axis" icon="sitemap" href="/platform/attribute/guides/create-a-variant-axis">
62
- Generate variants from a `multi_select` axis attribute.
67
+ Create an axis and build a product’s variants on it.
63
68
  </Card>
64
69
  </CardGroup>
65
70
 
66
71
  ## Resources
67
72
 
68
- Data models, links, workflows, service methods, and events for the Attribute
69
- domain.
73
+ The data models, links, workflows, service methods, and events behind attributes.
70
74
 
71
75
  <CardGroup cols={2}>
72
76
  <Card title="Data models" icon="table" href="/platform/attribute/reference/data-models">
73
- The `ProductAttribute` and `ProductAttributeValue` entities.
77
+ `ProductAttribute` and `ProductAttributeValue`, field by field.
74
78
  </Card>
75
79
  <Card title="Links" icon="link" href="/platform/attribute/reference/links">
76
- How the Attribute domain links to products, categories, and options.
80
+ Links to products, categories, and product options.
77
81
  </Card>
78
82
  <Card title="Workflows" icon="diagram-project" href="/platform/attribute/reference/workflows">
79
- Catalog and product-attachment workflows.
83
+ Catalog workflows and product attachment workflows.
80
84
  </Card>
81
85
  <Card title="Service" icon="gear" href="/platform/attribute/reference/service">
82
86
  Module service methods for working with records directly.
@@ -1,56 +1,71 @@
1
1
  ---
2
- title: "Data models"
2
+ title: "Attribute data models"
3
3
  sidebarTitle: "Data models"
4
- description: "The data models owned by the Attribute (Product Attribute) domain."
4
+ description: "Every field on ProductAttribute and ProductAttributeValue, with defaults, nullability, indexes, and relations."
5
5
  ---
6
6
 
7
- The Attribute domain is owned by the **Product Attribute module**. This reference
8
- lists its data models and their fields. For the full module overview, see the
9
- [Attribute module overview](/platform/attribute/overview).
7
+ The Product Attribute module owns 2 data models: `ProductAttribute` and `ProductAttributeValue`. Both are defined in `packages/core/src/modules/product-attribute/models` and resolved through `MercurModules.PRODUCT_ATTRIBUTE` (`"product_attribute"`). Like every Medusa data model, each also gets `created_at`, `updated_at`, and `deleted_at` timestamps.
10
8
 
11
9
  ## ProductAttribute
12
10
 
13
- Table `product_attribute`, id prefix `pattr`. A typed catalog attribute.
11
+ A typed attribute in the shared catalog, either global or scoped to one product. Id prefix `pattr`.
14
12
 
15
- | Field | Type | Notes |
13
+ | Field | Type | Description |
16
14
  | --- | --- | --- |
17
- | `id` | text | Primary key |
18
- | `name` | text | Searchable |
19
- | `handle` | text | Nullable; unique when set |
20
- | `description` | text | Nullable |
21
- | `type` | enum | `AttributeType`: `single_select`, `multi_select`, `text`, `unit`, `toggle` |
22
- | `is_required` | boolean | Default `false` |
23
- | `is_filterable` | boolean | Default `false`; exposes the attribute as a storefront filter |
24
- | `is_variant_axis` | boolean | Default `false`; `multi_select` axis that generates variants |
25
- | `rank` | number | Default `0`; ordering |
26
- | `is_active` | boolean | Default `true` |
27
- | `created_by` | text | Nullable |
28
- | `product_id` | text | Nullable; non-null = product-scoped (inline), null = global |
29
- | `product_option_id` | text | Nullable; FK to the mirror `ProductOption` (axis attributes) |
30
- | `metadata` | json | Nullable |
31
-
32
- Relations: `values` (one-to-many `ProductAttributeValue`, deleted with the
33
- attribute).
15
+ | `id` | `string` | Primary key, prefixed `pattr` |
16
+ | `handle` | `string \| null` | Unique among non-deleted attributes when set. Derived from `name` on create for global attributes |
17
+ | `name` | `string` | Display name. Searchable. For an axis, also the title of its `ProductOption` |
18
+ | `description` | `string \| null` | Free-form description |
19
+ | `type` | `AttributeType` | One of `single_select`, `multi_select`, `unit`, `toggle`, `text`. Can’t change after create |
20
+ | `is_required` | `boolean` | Default `false`. The remove workflows refuse to detach a required attribute |
21
+ | `is_filterable` | `boolean` | Default `false`. Marks the attribute as a storefront facet |
22
+ | `is_variant_axis` | `boolean` | Default `false`. With `multi_select`, backs the attribute with a `ProductOption` |
23
+ | `rank` | `number` | Default `0`. Sort order |
24
+ | `is_active` | `boolean` | Default `true` |
25
+ | `created_by` | `string \| null` | Id of whoever created the attribute. The built-in routes leave it unset |
26
+ | `product_id` | `string \| null` | `null` for a global attribute, the product’s id for an inline one |
27
+ | `product_option_id` | `string \| null` | Id of the mirror `ProductOption` for an axis attribute |
28
+ | `metadata` | `Record<string, unknown> \| null` | Custom key-value data |
29
+
30
+ Relations and constraints:
31
+
32
+ - **`values`**: has many `ProductAttributeValue`, deleted with the attribute
33
+ - **`IDX_product_attribute_handle_unique`**: unique `handle` where `deleted_at IS NULL AND handle IS NOT NULL`
34
+ - **`IDX_product_attribute_type`**: index on `type`
35
+ - **`IDX_product_attribute_product_id`**: index on `product_id` where it’s not null
34
36
 
35
37
  ## ProductAttributeValue
36
38
 
37
- Table `product_attribute_value`, id prefix `pattrval`. A selectable value that
38
- belongs to one attribute.
39
+ One selectable value of an attribute, or the stored value of a `text`, `unit`, or `toggle` attribute on a product. Id prefix `pattrval`.
39
40
 
40
- | Field | Type | Notes |
41
+ | Field | Type | Description |
41
42
  | --- | --- | --- |
42
- | `id` | text | Primary key |
43
- | `name` | text | The value label |
44
- | `handle` | text | Nullable; unique per attribute when set |
45
- | `rank` | number | Default `0`; ordering |
46
- | `is_active` | boolean | Default `true` |
47
- | `product_option_value_id` | text | Nullable; FK to the mirror `ProductOptionValue` (axis values) |
48
- | `metadata` | json | Nullable |
43
+ | `id` | `string` | Primary key, prefixed `pattrval` |
44
+ | `attribute_id` | `string` | Id of the parent `ProductAttribute` |
45
+ | `handle` | `string \| null` | Unique per attribute when set. Derived from `name` on create for values of global select attributes |
46
+ | `name` | `string` | The value label, or the stored content for `text` and `unit` |
47
+ | `rank` | `number` | Default `0`. Sort order within the attribute |
48
+ | `is_active` | `boolean` | Default `true` |
49
+ | `product_option_value_id` | `string \| null` | Id of the mirror `ProductOptionValue` for an axis value |
50
+ | `metadata` | `Record<string, unknown> \| null` | Custom key-value data |
51
+
52
+ Relations and constraints:
53
+
54
+ - **`attribute`**: belongs to `ProductAttribute` through `attribute_id`
55
+ - **`IDX_product_attribute_value_handle_unique`**: unique `(attribute_id, handle)` where `deleted_at IS NULL AND handle IS NOT NULL`
56
+
57
+ ## AttributeType enum
58
+
59
+ `AttributeType` is exported from `@mercurjs/types`:
49
60
 
50
- Relations: `attribute` (belongs to `ProductAttribute` via `attribute_id`).
61
+ | Member | Value |
62
+ | --- | --- |
63
+ | `SINGLE_SELECT` | `"single_select"` |
64
+ | `MULTI_SELECT` | `"multi_select"` |
65
+ | `UNIT` | `"unit"` |
66
+ | `TOGGLE` | `"toggle"` |
67
+ | `TEXT` | `"text"` |
51
68
 
52
69
  <Note>
53
- `product_option_id` on the attribute and `product_option_value_id` on the value
54
- are the foreign keys behind the read-only mirror links to Medusa's product
55
- options. See [Links](/platform/attribute/reference/links).
70
+ Neither model stores which products hold a value. That lives in the `product_attribute_value_link` pivot table, described in [Links](/platform/attribute/reference/links).
56
71
  </Note>