Attribute fields & types reference
Attributes are the custom, taxonomy-driven fields you attach to products — a Color dropdown, a Weight numeric, a Care instructions long-text block. This page is the field-by-field lookup for how an attribute is defined: its data type, its option values, its validation and display settings, and the edge-case behaviors (numeric precision formatting, force-delete guards, grouped listing) that you can't see from the form alone.
For the task of creating and editing attributes, see Manage attributes. For how attributes hang off categories and attribute groups, see How product taxonomy works and Manage attribute groups. To set attribute values on a specific product, see Set a product's categories & attributes.
Before you begin
Managing attribute definitions requires the products scope plus the relevant granular permission: attributes.create, attributes.update, attributes.archive, attributes.delete, or attributes.import. See Product permissions reference.
Data types
Every attribute has a Type that fixes how its value is stored, cast, and rendered. The default type is string.
Type (type) | Label in dialog | Stores | Notes |
|---|---|---|---|
string | String | Short single-line text | Default type. Can be flagged Is Link? (display.is_link) to render as a hyperlink. Supports a character limit. |
longtext | Longtext | Multi-line / rich text | Can be flagged Is HTML? (display.is_html). Supports a character limit. Has no Display (options) section. |
integer | Integer | Whole numbers | Supports min / max validation. |
numeric | Numeric | Decimal numbers | Supports min / max and a precision (decimal places). See Numeric precision formatting. Numeric attribute values can back a relative pricing tier basis. |
checkbox | Checkbox | A boolean | The value is cast to a real boolean when read on a product. Has no Display (options) section. |
date | Date | A date | Date-only value. |
datetime | — | A date + time | Supported by the back end and validation, but the attribute dialog's type list currently exposes only the six types above; use the API or import to set datetime. |
The back end accepts exactly these seven values (string, longtext, date, datetime, numeric, integer, checkbox); any other type is rejected with a validation error.
Checkbox is cast to boolean
When a product's value for a checkbox attribute is read, the value cast is switched to boolean — so a stored 1/0 comes back as true/false rather than a string. Aside from the numeric precision formatting described below, this is the only type whose product value is transformed away from its raw stored form.
Option values & options_type
Any non-checkbox, non-longtext attribute can be marked Has Options? to become a pick-list (for example a Color attribute with a fixed set of swatches). Options live as separate value rows on the attribute.
| Field | Where it lives | Meaning |
|---|---|---|
Has Options? (has_options) | display.has_options | Whether the attribute is a pick-list. This is a virtual attribute — there is no physical has_options column; reads and writes both go through the display JSON, so they can never diverge. |
Type (options_type) | display.type | Single (single-select) or Multi (multi-select). Defaults to Multi. |
Allow Custom Value? (allow_custom_value) | display.allow_custom_value | Whether a user can type a value outside the option list. |
| Option value | attribute_values.value | The option text itself (max 255 characters). Blank lines are ignored on save. |
| Option sort_order | attribute_values.sort_order | The display order of each option (0-based). |
In the dialog you type or paste options one per line into Type or Paste Options; each line becomes one option value with a sequential sort_order. On update, options you remove from the list are deleted, and options that remain are matched by their value text and kept.
The Display (options) section is hidden entirely for checkbox and longtext attributes — those types can't have options.
Validation, display & notes fields
The validation field is a free-form JSON object; only the keys relevant to the chosen type are read. The display field is a separate JSON object holding presentation flags. notes is free text.
| Field | Key | Applies to | Meaning |
|---|---|---|---|
| Character Limit | validation.limit | string, longtext | Maximum text length. |
| Precision (digits) | validation.precision | numeric | Decimal places to format the value to, 0–9. |
| Min | validation.min | integer, numeric | Minimum allowed value. |
| Max | validation.max | integer, numeric | Maximum allowed value. |
| Is Link? | display.is_link | string | Render the value as a hyperlink. |
| Is HTML? | display.is_html | longtext | Treat the value as HTML. |
| Sort Order | display.sort_order | all | The attribute's own position in a list (virtual attribute, stored inside display). |
| Available for templates | available_for_templates | all | See Template flag. |
| Notes | notes | all | Free-text note, max 255 characters. Internal — not shown on products. |
Field-level rules enforced on save:
| Rule | Field |
|---|---|
| Required, max 255, unique across attributes | name |
| Required, one of the seven types | type |
| Nullable array | display_options, validation |
Nullable, boolean when present | available_for_templates |
| Must reference an existing attribute group | attribute_group_id |
| Nullable, max 255 | notes |
The Validation section of the dialog is shown for string, longtext, numeric, and integer types only.
Numeric precision formatting
When you read an option value of a numeric attribute that has a validation.precision set, the value is passed through number_format() with that precision before it's returned.
- The returned value is a formatted string (for example precision
2turns a stored9.5into"9.50", and1234.5into"1,234.50"with a thousands separator). - Because it's formatted for display, it may not reflect the raw stored precision — a stored
9.567read with precision2comes back rounded to"9.57". - Formatting is applied only when both conditions hold: the attribute type is
numericandvalidation.precisionis present. Otherwise the raw stored value is returned unchanged.
Grouped + ungrouped listing
GET /api/attributes/grouped returns attributes organized for the taxonomy UI in two buckets:
| Key | Contents |
|---|---|
grouped | Each attribute group with its attributes nested inside (sorted by each attribute's sort_order). |
ungrouped | Attributes with no group — where attribute_group_id is null or 0. |
An attribute is considered ungrouped when its attribute_group_id is null or zero. On save, a 0 group id is normalized to null, so both forms are treated identically as "no group." To organize attributes into groups, see Manage attribute groups.
Template flag
available_for_templates is a boolean flag that marks an attribute as usable in product templates — the reusable starting points you build products from. It's a first-class field on the attribute (validated as boolean, cast to bool), independent of type, options, or group. Setting it doesn't change how the attribute behaves on an individual product; it only controls whether the attribute is offered when building from templates.
Force-delete with ignore_relations
Deleting an attribute normally checks whether anything still references it. Two independent kinds of usage are checked:
- Products — how many products carry a value for this attribute.
- Integration import mappings — any
DataImportMappingwhosesku_fieldisattributes.{name}, that is, a channel import that maps an incoming column onto this attribute.
The ignore_relations flag on the delete request changes the behavior:
| Situation | ignore_relations | Result |
|---|---|---|
| Attribute used by products and/or import mappings | absent / false | Blocked — 400 with a "currently used" message listing the relations. Nothing is deleted. |
| Attribute used by products only | true | Deleted. The product values and product links are detached and the attribute is removed. The response still returns an impact payload (products_count) so the downstream effect is acknowledged, never silent. |
| Attribute used by import mappings | true | Still blocked — 400 with the impact details. A force-delete can't bypass this, because deleting the attribute would silently break those channel import mappings. Remove or repoint the mappings first. |
Every delete response — allowed or blocked — carries an impact object with products_count, import_mappings_count, and the affected import_mappings, so the consequences of a force-delete are always surfaced rather than hidden. There is no separate secondary confirmation step; the impact payload is the acknowledgment.
To find what a delete would affect before you try it, the POST /api/attributes/deletable endpoint returns a deletable flag and reason per attribute id — see Manage attributes.
Next steps
- Manage attributes — the create / edit / archive / delete task steps.
- Manage attribute groups — group attributes for the taxonomy UI.
- Set a product's categories & attributes — attach attribute values to a product.
- How product taxonomy works — the category → attribute-group → attribute model.
- Relative pricing tiers — using a numeric attribute as a pricing basis.
- Import products from a spreadsheet — setting
attributes.{name}values in bulk.