@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.
- package/content/learn/architecture.mdx +166 -263
- package/content/learn/introduction.mdx +9 -1
- package/content/learn/use-cases.mdx +70 -0
- package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
- package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
- package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
- package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
- package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
- package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
- package/content/platform/attribute/overview.mdx +37 -33
- package/content/platform/attribute/reference/data-models.mdx +53 -38
- package/content/platform/attribute/reference/events.mdx +47 -24
- package/content/platform/attribute/reference/links.mdx +98 -27
- package/content/platform/attribute/reference/service.mdx +71 -32
- package/content/platform/attribute/reference/workflows.mdx +158 -28
- package/content/platform/catalog/concepts/master-products.mdx +39 -37
- package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
- package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
- package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
- package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
- package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
- package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
- package/content/platform/catalog/overview.mdx +39 -35
- package/content/platform/catalog/reference/data-models.mdx +154 -43
- package/content/platform/catalog/reference/events.mdx +41 -25
- package/content/platform/catalog/reference/links.mdx +196 -28
- package/content/platform/catalog/reference/service.mdx +57 -35
- package/content/platform/catalog/reference/workflows.mdx +124 -30
- package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
- package/content/platform/commission/concepts/rule-matching.mdx +53 -63
- package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
- package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
- package/content/platform/commission/guides/create-a-rate.mdx +101 -53
- package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
- package/content/platform/commission/overview.mdx +43 -35
- package/content/platform/commission/reference/data-models.mdx +59 -45
- package/content/platform/commission/reference/events.mdx +28 -34
- package/content/platform/commission/reference/links.mdx +42 -33
- package/content/platform/commission/reference/service.mdx +103 -28
- package/content/platform/commission/reference/workflows.mdx +101 -27
- package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
- package/content/platform/offer/concepts/shipping.mdx +28 -29
- package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
- package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
- package/content/platform/offer/guides/create-an-offer.mdx +112 -60
- package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
- package/content/platform/offer/overview.mdx +39 -32
- package/content/platform/offer/reference/data-models.mdx +77 -47
- package/content/platform/offer/reference/events.mdx +44 -18
- package/content/platform/offer/reference/links.mdx +92 -40
- package/content/platform/offer/reference/service.mdx +66 -31
- package/content/platform/offer/reference/workflows.mdx +146 -37
- package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
- package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
- package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
- package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
- package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
- package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
- package/content/platform/order-group/overview.mdx +42 -32
- package/content/platform/order-group/reference/data-models.mdx +48 -31
- package/content/platform/order-group/reference/events.mdx +29 -17
- package/content/platform/order-group/reference/links.mdx +90 -29
- package/content/platform/order-group/reference/service.mdx +55 -31
- package/content/platform/order-group/reference/workflows.mdx +83 -24
- package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
- package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
- package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
- package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
- package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
- package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
- package/content/platform/payout/overview.mdx +43 -34
- package/content/platform/payout/reference/data-models.mdx +63 -36
- package/content/platform/payout/reference/events.mdx +56 -30
- package/content/platform/payout/reference/links.mdx +52 -22
- package/content/platform/payout/reference/service.mdx +72 -33
- package/content/platform/payout/reference/workflows.mdx +80 -19
- package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
- package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
- package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
- package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
- package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
- package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
- package/content/platform/product-edit/overview.mdx +41 -34
- package/content/platform/product-edit/reference/data-models.mdx +73 -47
- package/content/platform/product-edit/reference/events.mdx +39 -29
- package/content/platform/product-edit/reference/links.mdx +32 -24
- package/content/platform/product-edit/reference/service.mdx +45 -26
- package/content/platform/product-edit/reference/workflows.mdx +173 -31
- package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
- package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
- package/content/platform/review/concepts/the-review-model.mdx +52 -45
- package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
- package/content/platform/review/guides/create-a-review.mdx +109 -39
- package/content/platform/review/guides/moderate-a-review.mdx +83 -37
- package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
- package/content/platform/review/overview.mdx +43 -33
- package/content/platform/review/reference/data-models.mdx +41 -27
- package/content/platform/review/reference/events.mdx +55 -37
- package/content/platform/review/reference/links.mdx +80 -22
- package/content/platform/review/reference/service.mdx +61 -33
- package/content/platform/review/reference/workflows.mdx +80 -24
- package/content/platform/store/concepts/lifecycle.mdx +68 -44
- package/content/platform/store/concepts/store-entity.mdx +45 -27
- package/content/platform/store/concepts/team.mdx +56 -35
- package/content/platform/store/guides/create-a-store.mdx +102 -35
- package/content/platform/store/guides/manage-the-team.mdx +122 -32
- package/content/platform/store/guides/moderate-a-store.mdx +107 -38
- package/content/platform/store/overview.mdx +41 -34
- package/content/platform/store/reference/data-models.mdx +132 -57
- package/content/platform/store/reference/events.mdx +42 -17
- package/content/platform/store/reference/links.mdx +67 -49
- package/content/platform/store/reference/service.mdx +105 -33
- package/content/platform/store/reference/workflows.mdx +304 -24
- package/content/references/api/conventions.mdx +20 -0
- package/content/resources/integrations/magento2.mdx +162 -0
- package/content/resources/integrations/overview.mdx +10 -0
- package/content/resources/integrations/stripe-connect.mdx +1 -1
- package/content/resources/tutorials/custom-api-route.mdx +3 -2
- package/llms.txt +113 -111
- package/package.json +1 -1
|
@@ -1,59 +1,86 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Attribute types"
|
|
2
|
+
title: "Attribute types and values"
|
|
3
3
|
sidebarTitle: "Attribute types"
|
|
4
|
-
description: "The five attribute types and the
|
|
4
|
+
description: "The five attribute types, how each one stores its values, and the flags every attribute carries."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
7
|
+
An attribute is a typed field in the shared catalog, and its type decides what a product can hold for it: a choice from a list, free text, a measurement, or a yes/no flag. Picking the right type matters because it fixes how values are stored and linked, and you can’t change it once the attribute exists.
|
|
8
|
+
|
|
9
|
+
## The attribute record
|
|
10
|
+
|
|
11
|
+
Every attribute is a `ProductAttribute` record (id prefix `pattr`) with a `name`, a `type`, and a few flags. Its selectable choices are separate `ProductAttributeValue` records (id prefix `pattrval`) that belong to it and are deleted with it.
|
|
12
|
+
|
|
13
|
+
```ts title="src/scripts/seed-attributes.ts"
|
|
14
|
+
import type { ExecArgs } from "@medusajs/framework/types"
|
|
15
|
+
import { AttributeType } from "@mercurjs/types"
|
|
16
|
+
import { createProductAttributesWorkflow } from "@mercurjs/core/workflows"
|
|
17
|
+
|
|
18
|
+
export default async function seedAttributes({ container }: ExecArgs) {
|
|
19
|
+
await createProductAttributesWorkflow(container).run({
|
|
20
|
+
input: {
|
|
21
|
+
attributes: [
|
|
22
|
+
{
|
|
23
|
+
name: "Material",
|
|
24
|
+
type: AttributeType.SINGLE_SELECT,
|
|
25
|
+
values: [{ name: "Cotton" }, { name: "Linen" }],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
}
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
The `
|
|
33
|
+
The script creates one global `single_select` attribute with two values. Because no `handle` is passed, the module derives `material` from the name.
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
35
|
+
## The five types
|
|
36
|
+
|
|
37
|
+
The `type` field takes one of the `AttributeType` enum values. Each type stores what a product holds in a different way:
|
|
38
|
+
|
|
39
|
+
| Type | Enum | A product holds | Where the value comes from |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| Single select | `single_select` | One choice from a fixed list | Values you create on the attribute |
|
|
42
|
+
| Multi select | `multi_select` | Several choices from a fixed list | Values you create on the attribute |
|
|
43
|
+
| Text | `text` | A free-form string | A new value created when you attach it |
|
|
44
|
+
| Unit | `unit` | A measurement, such as `400` | A new value created when you attach it |
|
|
45
|
+
| Toggle | `toggle` | `true` or `false` | The two values seeded when the attribute is created |
|
|
46
|
+
|
|
47
|
+
Only a `multi_select` attribute can be a [variant axis](/platform/attribute/concepts/variant-axes). The other four types describe a product but never shape its variants.
|
|
48
|
+
|
|
49
|
+
## How values link to products
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
A product doesn’t store attribute data on itself. Instead, each value it holds is linked to it through the product to value pivot link, and the shape of that link depends on the type:
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
deleted along with its parent attribute.
|
|
53
|
+
- **Select types**: the product links to existing values you pick by id
|
|
54
|
+
- **Text and unit**: attaching creates a new `ProductAttributeValue` named after the entered content, for example `"400"`, and links it
|
|
55
|
+
- **Toggle**: the product links to the seeded `"true"` or `"false"` value, and no new value is ever created
|
|
47
56
|
|
|
48
57
|
<Note>
|
|
49
|
-
`
|
|
50
|
-
`ProductAttributeValue` records. `text` and `unit` create a value on the fly
|
|
51
|
-
from the entered content when attached to a product. `toggle` is seeded with
|
|
52
|
-
its `true` / `false` values and never creates new ones.
|
|
58
|
+
`unit` stores the number as the value’s `name` string. The attribute has no separate unit-of-measure field, so put the unit in the attribute name, for example “Weight (kg)”.
|
|
53
59
|
</Note>
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
## Rules the module enforces
|
|
62
|
+
|
|
63
|
+
The Product Attribute service adds a few rules on top of the generated CRUD methods:
|
|
64
|
+
|
|
65
|
+
- **Type is fixed**: `updateProductAttributes` throws when you try to change the `type` of an existing attribute
|
|
66
|
+
- **Handles are derived**: a global attribute without a `handle` gets one from its name, and global select values get one from theirs
|
|
67
|
+
- **Handles are unique**: attribute handles are unique across the catalog, and value handles are unique per attribute
|
|
68
|
+
- **Toggle values are seeded**: creating a `toggle` attribute adds its `true` (rank 0) and `false` (rank 1) values
|
|
69
|
+
|
|
70
|
+
## Flags on every attribute
|
|
71
|
+
|
|
72
|
+
Each attribute carries flags that control where it shows up and how it’s treated:
|
|
73
|
+
|
|
74
|
+
| Field | Default | Effect |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `is_required` | `false` | The attachment workflows refuse to remove the attribute from a product |
|
|
77
|
+
| `is_filterable` | `false` | Marks the attribute as a storefront facet |
|
|
78
|
+
| `is_variant_axis` | `false` | Makes a `multi_select` attribute generate a `ProductOption` |
|
|
79
|
+
| `is_active` | `true` | A status flag you can filter attribute lists by |
|
|
80
|
+
| `rank` | `0` | Sort order for attributes and for values within an attribute |
|
|
81
|
+
|
|
82
|
+
## Related
|
|
83
|
+
|
|
84
|
+
- [Variant axes and Medusa product options](/platform/attribute/concepts/variant-axes)
|
|
85
|
+
- [Global and inline attributes](/platform/attribute/concepts/global-vs-inline)
|
|
86
|
+
- [Every field on the data models](/platform/attribute/reference/data-models)
|
|
@@ -1,50 +1,72 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Global
|
|
2
|
+
title: "Global and inline attributes"
|
|
3
3
|
sidebarTitle: "Global vs inline"
|
|
4
|
-
description: "Catalog-wide attributes,
|
|
4
|
+
description: "Catalog-wide attributes, attributes scoped to one product, category scoping, and storefront filters."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
attributes and how an attribute becomes a storefront filter.
|
|
7
|
+
Every attribute is either global, a catalog entry any product can reuse, or inline, a one-off field created on a single product. Global attributes keep the catalog consistent across stores. Inline attributes cover the odd field one product needs without adding noise to the shared vocabulary.
|
|
9
8
|
|
|
10
|
-
## Scope
|
|
9
|
+
## Scope is decided by product_id
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
`product_id` field:
|
|
11
|
+
The `product_id` field on `ProductAttribute` decides the scope. It’s `null` for a global attribute and holds the product’s id for an inline one:
|
|
14
12
|
|
|
15
|
-
| Scope | `product_id` |
|
|
16
|
-
| --- | --- | --- |
|
|
17
|
-
| Global | `null` |
|
|
18
|
-
| Inline |
|
|
13
|
+
| Scope | `product_id` | Created by | Listed in the catalog |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| Global | `null` | `createProductAttributesWorkflow` | Yes |
|
|
16
|
+
| Inline | `prod_…` | An inline entry in a product’s `add` list | No |
|
|
17
|
+
|
|
18
|
+
The admin, vendor, and store attribute list routes all filter on `product_id: null`, so inline attributes never appear in the catalog. You read them from the product through the `scoped_attributes` link instead.
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const { data: [product] } = await query.graph({
|
|
22
|
+
entity: "product",
|
|
23
|
+
fields: ["id", "scoped_attributes.name", "scoped_attributes.values.name"],
|
|
24
|
+
filters: { id: "prod_01JD8Q1234567" },
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Create an inline attribute
|
|
29
|
+
|
|
30
|
+
You create an inline attribute while you attach it. An `add` entry with a `title` instead of an `id` creates a product-scoped attribute, its values, and the links in one step:
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
{
|
|
32
|
+
```ts
|
|
33
|
+
await createAndLinkProductAttributesToProductWorkflow(container).run({
|
|
34
|
+
input: {
|
|
35
|
+
product_id: "prod_01JD8Q1234567",
|
|
36
|
+
add: [{ title: "Gift wrap", value: true }],
|
|
37
|
+
},
|
|
38
|
+
})
|
|
26
39
|
```
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
When you omit `type`, the workflow infers it: `is_variant_axis: true` makes a `multi_select` axis, a boolean `value` makes a `toggle`, and anything else makes a `text` attribute. Inline attributes get no derived handle, so their names never clash with the catalog.
|
|
42
|
+
|
|
43
|
+
The two scopes behave differently over their life:
|
|
44
|
+
|
|
45
|
+
- **Rename**: an `update` entry with a `title` renames an inline attribute and, for an axis, its exclusive option, but it’s ignored for a global attribute
|
|
46
|
+
- **Remove**: removing an inline attribute from its product deletes the attribute and its values, while removing a global one drops the product’s value links and, for an axis, detaches the option
|
|
47
|
+
- **Delete**: `deleteProductAttributesWorkflow` refuses to delete a global attribute while any of its values are linked to a product
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
Attributes describe products in the **shared master catalog**. They are never
|
|
35
|
-
owned by a store. A seller sells against a master product through an offer, and
|
|
36
|
-
the product's attributes come from the catalog, not from the seller.
|
|
37
|
-
</Note>
|
|
49
|
+
## Scope global attributes to categories
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
A global attribute can link to product categories through the `categories` link. The admin and vendor attribute lists accept a `product_category_id` filter and return two groups: attributes linked to that category, and attributes linked to no category at all. Category links change where an attribute is offered:
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
- **No categories**: the attribute is offered for every product
|
|
54
|
+
- **One or more categories**: the attribute is offered only for products in those categories
|
|
55
|
+
|
|
56
|
+
Pass `category_ids` to `createProductAttributesWorkflow` to create the links with the attribute.
|
|
57
|
+
|
|
58
|
+
## Storefront filters
|
|
59
|
+
|
|
60
|
+
Shoppers filter `GET /store/products` with `attributes[<handle>]=value` query parameters. The store middleware turns each one into a filter on `variants.options.value`, so filtering works on variant axis values. Several values for one attribute are OR’d, and separate attributes are AND’d.
|
|
61
|
+
|
|
62
|
+
`is_filterable` doesn’t change that query. It’s a flag your storefront reads from the store attribute list to decide which attributes to render as facets.
|
|
44
63
|
|
|
45
64
|
<Tip>
|
|
46
|
-
|
|
47
|
-
like Color both generates variants and lets shoppers filter by them. A
|
|
48
|
-
descriptive `text` attribute like Care instructions is usually left
|
|
49
|
-
non-filterable.
|
|
65
|
+
Mark axes such as Size and Color as filterable. Descriptive `text` attributes such as care instructions aren’t variant options, so the product filter can’t match them.
|
|
50
66
|
</Tip>
|
|
67
|
+
|
|
68
|
+
## Related
|
|
69
|
+
|
|
70
|
+
- [Attach attributes to a product](/platform/attribute/guides/attach-attributes-to-a-product)
|
|
71
|
+
- [Variant axes and Medusa product options](/platform/attribute/concepts/variant-axes)
|
|
72
|
+
- [The scoped_attributes and categories links](/platform/attribute/reference/links)
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Variant axes"
|
|
2
|
+
title: "Variant axes and product options"
|
|
3
3
|
sidebarTitle: "Variant axes"
|
|
4
|
-
description: "How
|
|
4
|
+
description: "How a multi_select axis attribute mirrors a native Medusa ProductOption, and how products pick a subset of its values."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
variants are generated from, and the mirror links that keep the two in sync.
|
|
7
|
+
A variant axis is a `multi_select` attribute that a product varies along, such as Size or Color. Mercur doesn’t invent its own variant system: each axis is backed by a native Medusa `ProductOption`, so variants, carts, and orders keep working the way Medusa expects while operators manage sizes and colors as catalog attributes.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
<img className="block dark:hidden" src="/images/figures/attribute-variant-axis-light.svg" alt="The Size axis attribute maps each value to a Medusa option value, S to optval_01JD8Q1 and so on, and the Linen shirt selects only S and M, so its variants behind it are S and M." />
|
|
10
|
+
<img className="hidden dark:block" src="/images/figures/attribute-variant-axis-dark.svg" alt="The Size axis attribute maps each value to a Medusa option value, S to optval_01JD8Q1 and so on, and the Linen shirt selects only S and M, so its variants behind it are S and M." />
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
## The option mirror
|
|
13
|
+
|
|
14
|
+
When you create a `multi_select` attribute with `is_variant_axis: true`, `createProductAttributesWorkflow` first creates a Medusa `ProductOption` (id prefix `opt`) titled with the attribute’s name. It then stores the option’s id on the attribute and each option value’s id on the matching attribute value.
|
|
15
|
+
|
|
16
|
+
| Attribute side | Field | Medusa side |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `ProductAttribute` | `product_option_id` | `ProductOption` |
|
|
19
|
+
| `ProductAttributeValue` | `product_option_value_id` | `ProductOptionValue` (id prefix `optval`) |
|
|
20
|
+
|
|
21
|
+
The workflows keep the two sides in step. Renaming the attribute renames the option, and adding, renaming, or deleting values rewrites the option’s value list. Deleting the attribute deletes the option.
|
|
18
22
|
|
|
19
23
|
```ts
|
|
20
24
|
await createProductAttributesWorkflow(container).run({
|
|
@@ -22,7 +26,7 @@ await createProductAttributesWorkflow(container).run({
|
|
|
22
26
|
attributes: [
|
|
23
27
|
{
|
|
24
28
|
name: "Size",
|
|
25
|
-
type:
|
|
29
|
+
type: AttributeType.MULTI_SELECT,
|
|
26
30
|
is_variant_axis: true,
|
|
27
31
|
values: [{ name: "S" }, { name: "M" }, { name: "L" }],
|
|
28
32
|
},
|
|
@@ -31,31 +35,49 @@ await createProductAttributesWorkflow(container).run({
|
|
|
31
35
|
})
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
selects along that axis are exactly what Medusa uses to generate its variants.
|
|
38
|
+
This run creates the `Size` attribute, a global `Size` option with values `S`, `M`, and `L`, and three attribute values pointing at those option values.
|
|
36
39
|
|
|
37
|
-
##
|
|
40
|
+
## Global options and exclusive options
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
the relationship is kept as a pair of **read-only mirror links**:
|
|
42
|
+
Since Medusa 2.16, a product option can be shared by several products, and products attach to it. Mercur uses both kinds of option Medusa offers:
|
|
41
43
|
|
|
42
|
-
|
|
|
43
|
-
| --- | --- | --- |
|
|
44
|
-
|
|
|
45
|
-
|
|
|
44
|
+
| Axis | Option created | `is_exclusive` | Shared by |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| Global axis attribute | When the attribute is created | `false` | Every product that attaches the axis |
|
|
47
|
+
| Inline axis attribute | When it’s added to one product | `true` | Only that product |
|
|
48
|
+
|
|
49
|
+
Medusa keeps titles of non-exclusive options unique, so two global axis attributes can’t share a name. Inline axes use exclusive options, so two products can each have their own “Cut” axis.
|
|
50
|
+
|
|
51
|
+
## A product picks a subset of values
|
|
52
|
+
|
|
53
|
+
A global axis holds every value the marketplace supports, but one product rarely uses all of them. When you attach the axis with `value_ids`, the workflow links the option to the product with only the matching option values, and it also links those attribute values to the product through the pivot link.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
await createAndLinkProductAttributesToProductWorkflow(container).run({
|
|
57
|
+
input: {
|
|
58
|
+
product_id: "prod_01JD8Q1234567",
|
|
59
|
+
add: [
|
|
60
|
+
{
|
|
61
|
+
id: "pattr_01JD8Q1234567",
|
|
62
|
+
value_ids: ["pattrval_01JD8Q1234567", "pattrval_01JD8Q1234568"],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The product now has the `Size` option restricted to two values. You change the subset later with an `update` entry. The workflow moves the option values and the pivot links together.
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
## Variants bind to the option by title
|
|
72
|
+
|
|
73
|
+
Attaching an axis doesn’t create variants. You create variants yourself, and each one names its option values by option title, for example `options: { Size: "M" }`. Mercur’s `createProductsWorkflow` takes `attributes` and `variants` together, attaches the axes first, and then creates the variants against them.
|
|
49
74
|
|
|
50
75
|
<Note>
|
|
51
|
-
|
|
52
|
-
attribute record; you never write the relationship through the link. The
|
|
53
|
-
workflows keep the option and the attribute in step whenever an axis attribute
|
|
54
|
-
or its values change.
|
|
76
|
+
A product with no axis still needs an option for Medusa to accept variants. `createProductsWorkflow` gives every new product a placeholder `__default__` option and removes it once a real axis is attached.
|
|
55
77
|
</Note>
|
|
56
78
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
## Related
|
|
80
|
+
|
|
81
|
+
- [Create a variant axis and build variants on it](/platform/attribute/guides/create-a-variant-axis)
|
|
82
|
+
- [The read-only mirror links](/platform/attribute/reference/links)
|
|
83
|
+
- [Global and inline attributes](/platform/attribute/concepts/global-vs-inline)
|
|
@@ -1,69 +1,112 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Attach attributes to a product"
|
|
3
3
|
sidebarTitle: "Attach to a product"
|
|
4
|
-
description: "Attach, detach, and update a product's attributes in one batch
|
|
4
|
+
description: "Attach, detach, and update a product's attributes in one run of the batch workflow."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
server code through a single batch workflow.
|
|
7
|
+
Manage every attribute on a product from server code with one workflow, `createAndLinkProductAttributesToProductWorkflow`, the same engine behind the admin attribute batch route.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
## Before you begin
|
|
10
|
+
|
|
11
|
+
You need the following:
|
|
12
|
+
|
|
13
|
+
- **A product**: the id of a product in the shared catalog, such as `prod_01JD8Q1234567`
|
|
14
|
+
- **Global attributes**: the ids of the catalog attributes and values you want to attach, created as in [Create a catalog attribute](/platform/attribute/guides/create-an-attribute)
|
|
15
15
|
|
|
16
16
|
## Run the batch workflow
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
<Steps>
|
|
19
|
+
<Step title="Send add, remove, and update in one input">
|
|
20
|
+
The workflow applies `remove`, then `add`, then `update`, so removing and re-adding the same attribute in one run works.
|
|
21
|
+
|
|
22
|
+
```ts title="src/api/admin/catalog-import/products/[id]/route.ts"
|
|
23
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
24
|
+
import {
|
|
25
|
+
createAndLinkProductAttributesToProductWorkflow,
|
|
26
|
+
} from "@mercurjs/core/workflows"
|
|
27
|
+
|
|
28
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
29
|
+
await createAndLinkProductAttributesToProductWorkflow(req.scope).run({
|
|
30
|
+
input: {
|
|
31
|
+
product_id: req.params.id,
|
|
32
|
+
add: [
|
|
33
|
+
{ id: "pattr_01JD8Q1234561", value_ids: ["pattrval_01JD8Q1234561"] },
|
|
34
|
+
{ id: "pattr_01JD8Q1234562", value: 400 },
|
|
35
|
+
{ title: "Gift wrap", value: true },
|
|
36
|
+
],
|
|
37
|
+
remove: ["pattr_01JD8Q1234563"],
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
res.sendStatus(200)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The first entry picks a value of a select attribute, the second sets a `unit` attribute to `400`, and the third creates an inline `toggle` attribute on this product.
|
|
46
|
+
</Step>
|
|
47
|
+
<Step title="Read the attributes back">
|
|
48
|
+
A product holds its selections as linked values. Read them through the `product_attribute_values` link, and read inline attributes through `scoped_attributes`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const query = req.scope.resolve("query")
|
|
52
|
+
|
|
53
|
+
const { data: [product] } = await query.graph({
|
|
54
|
+
entity: "product",
|
|
55
|
+
fields: [
|
|
56
|
+
"product_attribute_values.name",
|
|
57
|
+
"product_attribute_values.attribute.name",
|
|
58
|
+
"scoped_attributes.name",
|
|
33
59
|
],
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})
|
|
60
|
+
filters: { id: req.params.id },
|
|
61
|
+
})
|
|
62
|
+
```
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
64
|
+
Inline attributes also show up under `product_attribute_values`, because their values link to the product the same way.
|
|
65
|
+
</Step>
|
|
66
|
+
</Steps>
|
|
42
67
|
|
|
43
|
-
##
|
|
68
|
+
## Choose the add form
|
|
44
69
|
|
|
45
|
-
Each
|
|
70
|
+
Each `add` entry is a `ProductAttributeBatchAdd`. The keys you pass decide what the workflow does:
|
|
46
71
|
|
|
47
72
|
| Form | Shape | Effect |
|
|
48
73
|
| --- | --- | --- |
|
|
49
|
-
| Existing select
|
|
50
|
-
| Existing
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
| Existing select | `{ id, value_ids }` | Links the chosen values to the product |
|
|
75
|
+
| Existing axis | `{ id, value_ids }` | Attaches the global option with only those values, and links the values |
|
|
76
|
+
| Existing text or unit | `{ id, value }` | Creates a value named `String(value)` and links it |
|
|
77
|
+
| Existing toggle | `{ id, value }` | Links the seeded `"true"` or `"false"` value |
|
|
78
|
+
| Inline attribute | `{ title, type?, value }` or `{ title, type?, values }` | Creates a product-scoped attribute and its values, and links them |
|
|
79
|
+
| Inline axis | `{ title, values, is_variant_axis: true }` | Creates an exclusive option, a scoped `multi_select` attribute, and the mirrored values |
|
|
80
|
+
|
|
81
|
+
## Change a selection with update
|
|
82
|
+
|
|
83
|
+
An `update` entry targets one attribute by `id`. What `add` and `remove` mean depends on the attribute:
|
|
84
|
+
|
|
85
|
+
- **Global select or axis**: `add` and `remove` hold attribute value ids, and an axis also moves the option values
|
|
86
|
+
- **Global select or axis, full set**: `value_ids` is the complete set the product should hold, and the workflow works out the difference
|
|
87
|
+
- **Inline axis**: `add` holds new values such as `{ value: "XL" }`, and `remove` holds attribute value ids to delete
|
|
88
|
+
- **Text, unit, or toggle**: `value` replaces the current value
|
|
89
|
+
- **Inline rename**: `title` renames an inline attribute, and it’s ignored for global ones
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
update: [
|
|
93
|
+
{ id: "pattr_01JD8Q1234561", value_ids: ["pattrval_01JD8Q1234562"] },
|
|
94
|
+
{ id: "pattr_01JD8Q1234564", title: "Fit", add: [{ value: "Relaxed" }] },
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The first entry swaps the product to a different value. The second renames an inline axis and adds a value to its exclusive option.
|
|
99
|
+
|
|
100
|
+
## Handle a required attribute
|
|
101
|
+
|
|
102
|
+
Removing an attribute with `is_required: true` fails with a `NOT_ALLOWED` error, and nothing in the run is applied. The check skips attributes you remove and re-add in the same run, so you can replace a required attribute’s selection in one call.
|
|
103
|
+
|
|
104
|
+
## Route vendor edits through review
|
|
105
|
+
|
|
106
|
+
Running the batch workflow writes to the product immediately. For changes a store makes, run `productEditUpdateAttributesWorkflow` with the same `add`, `remove`, and `update` plus `created_by`. It stages a product change for review, and the change runs the batch workflow when it’s confirmed. See [Product edits](/platform/product-edit/overview).
|
|
107
|
+
|
|
108
|
+
## Next steps
|
|
109
|
+
|
|
110
|
+
- [Create a variant axis and build variants on it](/platform/attribute/guides/create-a-variant-axis)
|
|
111
|
+
- [Every attachment workflow and its input](/platform/attribute/reference/workflows)
|
|
112
|
+
- [The links behind product attributes](/platform/attribute/reference/links)
|