Product Taxonomy¶
A taxonomy classifies products into a tree of levels and gives each level typed attributes. It feeds three things: the filters on category pages and search (via the search index), the attributes shown on product pages, and a per-product completeness score. How to use the editor is in the admin guide; this page is the model and the rules behind it.
Model¶
All in common/.../taxonomy/.
| Type | Holds |
|---|---|
Taxonomy |
name, description, catalogueId, rootLevels (the tree), a revision counter and the dates. Stored in the taxonomy collection |
TaxonomyLevel |
levelId, name, description, parentLevelId, attributes, children, displayOrder |
TaxonomyAttribute |
attributeId, name, helpText, type, allowedValues, validation, displayOnProductPage, displayLabel, displayOrder |
AttributeValidation |
mandatory, minLength, maxLength, minValue, maxValue, pattern, errorMessage |
Types are STRING, NUMBER, BOOLEAN, SINGLE_SELECT and MULTI_SELECT. A product holds taxonomyAssignment (levelId, completenessScore) and taxonomyAttributes, a map keyed by attribute id: a string, a number, a boolean, or a list of strings for a multi-select.
The active taxonomy is the site configuration key taxonomy-active-id (ConfigKeyDefaults.ACTIVE_TAXONOMY_ID), not the active flag on the document, which is only a legacy soft-delete marker. TaxonomyService.getActiveTaxonomy returns null if no id is configured, if it no longer exists, or if the shop's plan lacks the TAXONOMY_ENGINE capability (Subscription Entitlements). findActiveTaxonomyId reads the configured id regardless of plan, which is what the admin list uses.
Inheritance¶
A product's applicable attributes are those of every level from the root down to its level (calculateApplicableAttributes). If a nearer level defines an attribute with the same id as an ancestor's, the nearer one wins. An attribute id therefore only has to be unique within a level; the same id on two sibling levels is one facet.
Changing the active taxonomy¶
TaxonomyService.activate(siteContext, id) sets the key. If a different taxonomy was active it publishes ActiveTaxonomyChangedEvent, and TaxonomyArchivalListener copies every product's assignment and attributes into archivedTaxonomyData and clears the live fields in one server-side update. There is one archive slot, so a second switch overwrites it; re-activating an old taxonomy does not restore anything. The first activation publishes nothing. The admin therefore requires admin roles and a typed confirmation, and the engine entitlement.
Saving a structure¶
TaxonomyService.saveStructure(siteContext, id, name, description, rootLevels, expectedRevision) is the single write path for the editor. The tree is first normalised (TaxonomyStructure.normalise) and then validated (TaxonomyStructure.validate):
- Parent ids and sibling order are recomputed from the nesting; the client's values are not trusted.
- New levels and attributes (ids blank or starting
new-) get a slug of their name, made unique. A generated attribute id avoids ids defined on ancestors so it can't quietly override one. Existing ids are never changed. - Names are trimmed, allowed values are trimmed and de-duplicated, an empty validation object is dropped.
- Validation reports every problem at once: missing or duplicate level names among siblings, duplicate level ids, a parent that is not the level's actual parent, cycles, depth over 8, more than 2000 levels, attribute ids repeated on one level, ids that aren't
[A-Za-z0-9][A-Za-z0-9_-]*(existing ids are exempt), choice attributes with no values, values on non-choice attributes, blank, duplicate or comma-containing values,min > max, and patterns that don't compile or are too long.
Concurrency. Taxonomy.revision is incremented on every write (save and saveStructure). The editor sends the revision it loaded; saveStructure does a conditional updateFirst matching _id, catalogue and revision (a document from before the field existed counts as 0). If nothing matches, a TaxonomyConflictException is thrown and nothing is written, so two racing saves cannot both succeed. The repository cache is cleared afterwards, and a TaxonomyIndexEvent is published.
deleteTaxonomy is scoped to the site's catalogue and refuses the active taxonomy; TaxonomyRepository.deleteById now evicts the taxonomy cache like save does.
Impact queries¶
TaxonomyImpactService answers "who is affected" without loading products:
directCounts/rolledUpCounts: products per level (and including sub-levels), from one aggregation (ProductService.countByTaxonomyLevel). Empty unless the taxonomy is the active one, because only the active taxonomy's data is on products.previewChanges: compares a proposed tree to the stored one by id and reports levels removed (products there), levels moved (products below), attributes removed and allowed values removed that products hold, each with a count. Up to 200 removed values are counted per preview.previewActivation: whether activating would switch, and how many products carry a classification that would be cleared.
Product edit¶
ProductTaxonomyService.applyAdminSubmission applies the product form's Taxonomy tab with the same rules as the MCP manageProductTaxonomy tool: the level must exist in the active taxonomy; only applicable attributes are kept; values are coerced to type (numbers, booleans, lists); each is validated; a value that breaks its rules is dropped and reported rather than blocking the save; completeness is recomputed. The tab only applies if the form carries taxonomyPanelSubmitted, so a page rendered without an active taxonomy never wipes a product's classification.
TaxonomyService.validateProductAttributes now checks an attribute's type (allowed values, number format) even when it has no validation rules object; previously such attributes accepted anything.
Admin endpoints¶
All same-origin JSON under /admin/catalogue/taxonomy (TaxonomyEditorRestController; merchandiser, admin, super admin unless noted). They are not part of the public REST API, which still serves /v1/admin/taxonomies for external clients.
| Method and path | Purpose |
|---|---|
GET /{id}/data |
Tree, revision and product counts for the editor |
PUT /{id}/structure |
Save name, description and tree. 400 with errors, 409 if stale |
POST /{id}/impact |
Preview what a tree would leave behind on products |
POST /create, POST /{id}/duplicate |
Create (blank or sample) and copy |
DELETE /{id} |
Delete (admin); 409 for the active taxonomy |
GET /{id}/activation-impact, POST /activate/{id} |
Activate (admin). Switching needs confirmName equal to the target's name |
GET /admin/catalogue/product/taxonomy/fields?levelId= |
The attribute inputs for a level, as HTML, for the product page |
The editor's reindex banner calls POST /admin/search/rest/reindex with scope PRODUCTS.