Which built-in type fits which purpose, how categories, tags, slugs, custom URLs and SEO settings behave across them, how revisions and publishing gate what the public sees, how to model a domain the platform has no type for, and where uploaded files live.
L1–L3
Unit 02 · Taxonomy, URLs and SEO
Categories and tags
Taxonomy, URLs and SEOPage 7 of 35Updated
Categories form a tree and tags are flat, and both are shared by every content type that offers them. The rule for choosing between them, building a category tree and rendering its landing pages, and filtering listings by tag across several content types, in Smarty and Twig.
Who this is for. The content modeller designing a site's taxonomy and the theme developer building listings and facets on it. Afterwards you can choose between a category and a tag with a reason, and render category landing pages and tag filters.
The rule: a category is structural, a tag is descriptive
A category answers "where does this belong" and sits in a tree, so it can have a parent and children. A tag answers "what is this about", is flat, and a record has as many as apply. If you find yourself wanting a tag to have children, it is a category; if a category tree grows a branch for every adjective, those branches are tags.
Categories
Categories are managed in the admin under Content, Common, Categories. A category has a name, a slug, a parent, children and, optionally, custom fields. It belongs to one site, or to every site of the account when its site is left empty. There is one tree for all content types: a category called News can be given to a page, a post, an FAQ question and a file alike. Which types offer the field is a setting of each module: on by default for pages, blog posts and FAQ questions, off by default for blogs, books, files, and custom entity definitions (their Categorizable switch). Product categories are a separate tree with descriptions and layouts of their own (The catalogue).
A category has no description field. A description, a landing image or a colour is a custom field on categories, read with getCustomFieldText() and its siblings. A name must be unique among its siblings, which the admin checks, and the database also refuses the same name twice on one site, so two categories called Accessories under different parents cannot both be saved: name them by their place (Bike accessories).
A category gives a template getName(), getSlug(), getParent(), getChildren() and the custom field getters. It has no getAllParents() and no URL of its own: its landing page is a listing of the module whose records it groups.
A category landing page
The listing routes take categories: /blogs/<blog>/categories/<slug>, /faq/questions/categories/<slug> and /<path>/categories/<slug> for a custom entity. On those routes the template receives the records as usual plus categories, and category when the URL names one. A blog's category landing page, with the category's description from a custom field and links to its subcategories:
Template codeblogs/posts/index.html, on a category route
{% if category is defined and category %}
<h1>{{ category.getName()|e }}</h1>
<p>{{ category.getCustomFieldText('description', '')|e }}</p>
{% for child in category.getChildren() %}
<a href="{{ blog.getCategoryPostsUrl(child) }}">{{ child.getName()|e }}</a>
{% endfor %}
{% else %}
<h1>{{ blog.getName()|e }}</h1>
{% endif %}
{% for post in posts %}
<h2><a href="{{ post.getFullPath() }}">{{ post.getName()|e }}</a></h2>
{% endfor %}
The description custom field is one you define on categories; without it, getCustomFieldText() returns the default you pass. A blog's category listing with no posts answers 404 rather than an empty page; FAQ and custom entity listings render with an empty list.
Tags
A tag has a name, a slug and, optionally, custom fields, and no parent. Like a category, it belongs to one site or to all of them, and the same tag can be given to several content types, which makes tags the tool for a facet that spans them: returns on questions, posts and store records at once. A tag's name is unique on its site. Tags are offered by default on pages, blog posts and FAQ questions; for blogs, books, files and custom entity definitions (Taggable) they are switched on per module.
Filtering by URL. The listing routes filter by tag: /blogs/news/tags/summer+sale requires both tags, /blogs/news/tags/summer,sale either. & and | mean the same as + and ,, and mixing AND with OR is a 400. Custom entity listings accept & and | only. When the URL names a single tag the template also receives tag.
Filtering across types. A facet over several content types queries each type with the tag as a criterion:
Each call is one query, and each passes the order and the limit to the database rather than sorting afterwards. store is a custom entity identifier of your own. The criteria accept the same operator prefixes as the API: '|tags.slug' => ['returns', 'refunds'] matches either tag.
A tag set that wants to be a tree
Tags named widgets-blue, widgets-red and widgets-large are a category tree written flat. Nothing can render them as a tree later, and a listing of "all widgets" has to know every name. Make Widgets a category with children, and keep tags for the descriptions that cut across it.