How product taxonomy works
Taxonomy is how you organize the catalog: brands say who makes a product, categories place it in a hierarchy, and attributes record its custom properties. These three systems are defined once, globally, and then assigned to individual products. Once assigned, they quietly power far more than the product page — pricing math, channel listing mappings, and brand/category reporting all read from taxonomy.
This page explains the model and its cross-system effects. To actually assign taxonomy to a product, see Set a product's categories & attributes; to manage the global definitions, see Manage brands, Manage categories, Manage attributes, and Manage attribute groups.
The Settings-vs-detail seam
Every taxonomy object has two homes, and knowing which is which prevents most confusion:
- Settings is where the definitions live. Creating, renaming, archiving, and deleting a brand, category, attribute, or attribute group all happen under Settings. These are global — one brand named "Acme" is shared by every product that uses it.
- The product detail page is where you assign those definitions to one product. The brand goes on the Overview tab; categories and attributes live on the product's taxonomy area. Assigning "Acme" to a product doesn't create a new brand — it points the product at the existing one.
So a rename in Settings ripples to every product at once, while an assignment on the detail page touches only that one product. When you follow a link from a product's brand or category into Settings, you are crossing this seam from assignment into definition.
Brands
A brand is a lightweight record — essentially a unique name. A product carries at most one brand through its brand_id, and a brand has many products. Brand names are globally unique: you can't create two brands with the same name.
Because a brand is referenced by products, SKU.io guards deletion. A brand that's still assigned to any product can't be deleted — the delete is refused and tells you how many products still use it. Clear the brand from those products first (or reassign them), then delete.
Renaming a brand in Settings changes it everywhere the brand appears, since every product simply points at the one shared record.
The brand scorecard, leaderboard, purchase-order history, product drilldowns, and growth heatmaps are reporting features, not taxonomy. They live under the reporting area (Reports → Brands) and are built from snapshot data you can rebuild there. Assigning a brand to a product is taxonomy; analyzing that brand's performance is reporting. See Products, channels & reporting for the reporting seams.
The category hierarchy
Categories form a tree. Each category has an optional parent_id; a category with no parent is a root. Several fields on a category are derived automatically — you never set them by hand:
| Field | Meaning | How it's set |
|---|---|---|
path | The full breadcrumb, for example, Apparel -> Tops -> T-Shirts | Materialized string rebuilt every time the category is saved |
level | Depth in the tree (number of ancestors including itself) | Derived from the path length |
root_id | The top-most ancestor's id (null if the category is a root) | Derived from the first node in the path |
is_leaf | Whether this category has no children | Flipped automatically as children are added or removed |
When you rename or re-parent a category, SKU.io rebuilds that category's path, level, and root_id, then walks its descendants and rebuilds each of their paths too, so the whole subtree stays consistent.
The single most important rule of the hierarchy is the leaf rule: only a leaf category (one with no children) can hold products, and only a leaf category can be deleted. A category that has subcategories is a container, not a bucket for products. If you try to delete a non-leaf category, the delete is refused; the error tells you exactly how many subcategories and how many products sit under it so you know what's in the way. Deleting the last child of a parent flips that parent back to a leaf automatically.
The silent "Other" auto-creation
The leaf rule creates one non-obvious behavior worth understanding, because it moves products without asking.
Suppose category Tops is a leaf and already has 40 products assigned to it. You then add a subcategory T-Shirts under Tops. Tops can no longer be a leaf — but it still has 40 products that must live somewhere legal. So when you add that first child, SKU.io automatically:
- Creates a new leaf subcategory named Other under Tops.
- Moves all 40 of Tops' products into Other.
- Flips Tops to a non-leaf container.
This keeps every product on a valid leaf without ever losing an assignment. The app surfaces a notification telling you an Other bucket was created and how many products moved, so the reorganization isn't invisible — but it does happen as a side effect of adding the child, not as a separate prompt you confirm. If a parent had no products when you added its first child, no Other bucket is created.
Primary vs additional categories
A product can belong to more than one category, but exactly one of its categories is the primary one. This is stored on the product_to_categories pivot as an is_primary flag: one row per product is marked primary, the rest are "additional" (sometimes shown as Other).
The primary category is the one shown prominently as the product's category path, and it's the one downstream logic reaches for when it needs a single canonical category — for example, resolving a product's category breadcrumb walks up the tree from the primary category. Additional categories let a product appear under several places in the hierarchy without any of them being ambiguous about which is canonical.
See Set a product's categories & attributes for how to choose the primary category and add additional ones.
Attributes
An attribute is a custom property you define once and then record per product — think Material, Voltage, Thread Count, Is Waterproof. Each attribute has a type that governs how its value is entered and validated:
| Type | Stores |
|---|---|
string | Short single-line text |
longtext | Multi-line text |
numeric | Decimal numbers (can carry a precision) |
integer | Whole numbers |
date | A calendar date |
datetime | A date and time |
checkbox | A true/false value |
An attribute can stand alone or belong to one attribute group. Optionally, an attribute can define a fixed list of predefined option values (a dropdown) — for example a Size attribute offering S, M, L, XL. Numeric option values respect the attribute's configured precision when displayed.
The actual value for a product lives on the product_attributes pivot — one row per (product, attribute) pair. So the attribute Material is defined once globally, but each product records its own value ("Cotton", "Leather", …).
Full field-by-field details are in the Attribute fields & types reference.
Deleting an attribute is guarded
Because attribute values are attached to products — and because attributes are also referenced by channel listing mappings (see below) — deleting an attribute that's still in use is refused by default. The refusal reports how many products use it and which integration import mappings reference it, so you can see the blast radius before forcing the delete.
Attribute groups
An attribute group bundles related attributes so they can be applied together — a "Dimensions" group holding Length, Width, Height, for instance. A group owns many attributes (each attribute's attribute_group_id points back at its group).
Groups reach products by two paths, and both count:
- Directly — a product records values for the group's attributes on the
product_attributespivot. - Through categories — a group can be attached to a category (
categories_to_attribute_groups), so every product in that category inherits the group.
When a group's list shows how many products it touches, that count is a combined sub-query summing both the direct product assignments and the through-category reach — so a group attached to a busy category shows a high usage count even if no product references it directly. That combined count is also what blocks deletion: a group still reaching products by either path can't be deleted until it's detached. See Manage attribute groups.
How taxonomy feeds the rest of the system
Taxonomy isn't inert labeling — several systems read it.
Numeric attributes can drive pricing
If you use attribute-relative pricing tiers, a pricing tier can be based on a numeric attribute (its basis). When you change that numeric attribute's value on a product, SKU.io automatically recomputes the affected relative prices in the background. Two guardrails apply:
- Only numeric values trigger the recompute. Editing a text, date, or checkbox attribute never recalculates pricing.
- The recompute is suppressed while prices are already propagating, so a cascade of related updates can't loop back on itself.
The mechanics of relative pricing live in Relative pricing tiers and Manage pricing tiers — this page only notes that a numeric attribute edit is a pricing trigger.
Attributes back channel listing mappings
When you map product data onto a sales channel, those field mappings can reference an attribute by name. This means renaming or deleting an attribute can silently break a channel listing mapping that pointed at the old name — which is exactly why attribute deletion reports the affected import mappings before it lets you proceed. Channel listing and publishing itself is a separate topic, covered in the Listings guide (coming separately); here it's enough to know attributes are a shared dependency of it.
Brands and categories feed reporting
Brand- and category-based reports — financials by brand, contribution margin by brand, and inventory-valuation category breakdowns — all consume taxonomy but are owned by the reporting area, not the catalog. Getting brands and categories right is what makes those reports meaningful. The reporting seams are summarized in Products, channels & reporting.
For how a product carries its taxonomy through orders, stock, and fulfillment, see How a product flows through the system. For live stock numbers, opening balances, and FIFO, see View a product's stock and the movement ledger.
Two API generations behind one model
You may notice the taxonomy screens are served by two families of endpoints. This is an implementation seam, not two different data sets:
- V2 Settings endpoints own the paginated list, detail, export, and activity-log views — the modern Vue-3 Settings screens read from these.
- Legacy resource endpoints still own create, update, delete, archive, bulk actions, and import. The legacy resource routes deliberately exclude the index action, because listing moved to V2.
Both generations read and write the same underlying tables and models, so a brand created through a legacy create endpoint appears immediately in the V2 list. You don't choose between them — the UI routes each action to the right one. It's worth knowing only because the same object can appear under two URL shapes.
Next steps
- Set a product's categories & attributes — assign taxonomy to one product
- Manage brands, Manage categories, Manage attributes, Manage attribute groups — the global definitions
- Attribute fields & types reference — every attribute type and its rules
- Relative pricing tiers — how numeric attributes drive pricing
- Products, channels & reporting — where taxonomy meets listings and reports