Skip to content

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.