> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mercurjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Product Attribute module

> Data models, links, and service methods for the Product Attribute module.

The Product Attribute module owns configurable **attributes** and their allowed **values** — the structured properties that describe products (material, color, size, condition, …). Attributes can be global (shared across the catalog) or product-scoped, can be restricted to categories, and — for `multi_select` attributes — can act as **variant axes** backed by Medusa's native global product options.

## Data models

### `ProductAttribute`

Table `product_attribute`, ID prefix `pattr`.

| Field               | Type    | Notes                                                                      |
| ------------------- | ------- | -------------------------------------------------------------------------- |
| `name`              | text    | Searchable                                                                 |
| `handle`            | text    | Nullable; unique when set; auto-generated for global attributes            |
| `description`       | text    | Nullable                                                                   |
| `type`              | enum    | `AttributeType`; immutable after creation                                  |
| `is_required`       | boolean | Default `false`                                                            |
| `is_filterable`     | boolean | Default `false`; filterable attributes are tokenized into the search index |
| `is_variant_axis`   | boolean | Default `false`; only valid for `multi_select`                             |
| `rank`              | number  | Default `0`; display ordering                                              |
| `is_active`         | boolean | Default `true`                                                             |
| `product_id`        | text    | Nullable — `null` means global, set means product-scoped (inline)          |
| `product_option_id` | text    | Nullable; mirror to the native `ProductOption` for axis attributes         |
| `created_by`        | text    | Nullable                                                                   |
| `metadata`          | json    | Nullable                                                                   |

### `ProductAttributeValue`

Table `product_attribute_value`, ID prefix `pattrval`. Unique on (`attribute_id`, `handle`) when the handle is set.

| Field                     | Type    | Notes                                                 |
| ------------------------- | ------- | ----------------------------------------------------- |
| `name`                    | text    | Display label                                         |
| `handle`                  | text    | Nullable; auto-generated for global select attributes |
| `rank`                    | number  | Default `0`                                           |
| `is_active`               | boolean | Default `true`                                        |
| `product_option_value_id` | text    | Nullable; mirror to the native `ProductOptionValue`   |
| `metadata`                | json    | Nullable                                              |

## Enums

```ts theme={null}
enum AttributeType {
  SINGLE_SELECT = "single_select",
  MULTI_SELECT = "multi_select",
  UNIT = "unit",
  TOGGLE = "toggle",
  TEXT = "text",
}
```

## How each type is stored

| Type                                        | Variant axis? | Backing store                                                                       |
| ------------------------------------------- | ------------- | ----------------------------------------------------------------------------------- |
| `multi_select` (axis)                       | yes           | Native Medusa `ProductOption` + option values; the product links a subset of values |
| `multi_select` / `single_select` (non-axis) | no            | Product ↔ value link rows                                                           |
| `text` / `unit`                             | no            | A value record is created for the entered text, then linked                         |
| `toggle`                                    | no            | Two seeded fixed values (`true` / `false`); the matching one is linked              |

Only `multi_select` attributes may be variant axes. A global axis maps to a shared (`is_exclusive: false`) product option; an inline, product-scoped axis maps to an exclusive option.

## Links

| Link                            | Purpose                                                                          |
| ------------------------------- | -------------------------------------------------------------------------------- |
| `product_category_attribute`    | Restricts an attribute to categories (many-to-many)                              |
| `product_attribute_value_link`  | Product ↔ selected values (non-axis selections, and a mirror of axis selections) |
| `scoped_attributes` (read-only) | Product → its product-scoped attributes via `product_id`                         |
| Option mirror (read-only)       | `ProductAttribute.product_option_id` → native `ProductOption`                    |
| Option-value mirror (read-only) | `ProductAttributeValue.product_option_value_id` → native `ProductOptionValue`    |

## Service

`ProductAttributeModuleService` extends `MedusaService` with auto-generated CRUD, plus:

| Method                                                    | Behavior                                                                                          |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `createProductAttributes(data)`                           | Auto-generates handles for global attributes; seeds `true`/`false` values for `toggle` attributes |
| `updateProductAttributes(data)`                           | Rejects any attempt to change an attribute's `type`                                               |
| `listProductAttributes` / `listAndCountProductAttributes` | Attach `values` only for select/toggle types; other types return `values: []`                     |
| `createProductAttributeValues(data)`                      | Auto-generates value handles for global select attributes                                         |

## Attaching attributes to products

Attributes are applied to products through the batch endpoint rather than the module service directly:

```
POST /admin/products/:id/attributes/batch
POST /vendor/products/:id/attributes/batch
```

The body is `{ add?, remove?, update? }`, applied in the order remove → add → update by the `createAndLinkProductAttributesToProductWorkflow`. Product create and update payloads also accept a unified `attributes[]` array.

Product GET responses expose the attribute graph as native `options` (variant axes), `product_attribute_values` (each with its parent `attribute` and, for global selects, `all_values`), and `scoped_attributes`.

## Related endpoints

* `GET/POST /admin/product-attributes`, value sub-routes — catalog management
* `GET /vendor/product-attributes` — category-filtered read access for sellers
* `GET /store/product-attributes` — storefront filter facets

## Next steps

<CardGroup cols={2}>
  <Card title="Product Edit module" href="/rc/references/modules/product-edit" />

  <Card title="Search module" href="/rc/references/modules/search" />
</CardGroup>
