CMS Chronicle #02: Tags Cross-Collection
A CMS without tags is a filing cabinet without labels. We added tags to @webhouse/cms this week — as a first-class field type with cross-collection querying built in.
The idea came up naturally. We were looking at the Articles section on webhouse.dk — four series, posts growing — and the obvious question surfaced: what if a post fits more than one series? What if you want to find everything we've written about "ai" or "architecture" regardless of which series it lives in?
A CMS without tags is a filing cabinet without labels. You know roughly where things are, but you can't pull everything related to a topic in one move.
What we built
Tags in @webhouse/cms work as a first-class field type:
- A
tagsfield stores a string array on any document in any collection. findMany()now accepts atagsfilter — documents must contain ALL specified tags (AND logic).findByTag(tag, collections?)searches across multiple collections in a single call.getAllTags()on the webhouse-site returns a deduplicated, sorted list of every tag in use.
The implementation was small — about 30 lines across four files — because the architecture was already right. Tags are just data. The findMany() filter chain already handled status. Adding tags was one more filter step.
The AND logic decision
When you pass multiple tags to findMany(), the query uses AND — a document must have all of them. This is the stricter, more precise behaviour. If you want OR (any of these tags), you call findByTag() once per tag and merge the results. The primitive is composable.
What it enables
Tag pages are the obvious next step: /tags/ai lists every post and case study tagged ai, across all collections. Cross-series discovery. A reader following a topic, not a series.
We'll build that page when there's enough tagged content to make it useful. For now, the infrastructure is there — and the second CMS Chronicle post is itself tagged cms, architecture, and tags.
The pattern
Notice what we didn't build: a separate tags collection, a join table, a tagging service. Tags are plain string arrays on documents. The query layer knows how to filter on them. That's it.
“Complexity is easy to add later. Simplicity is hard to recover once you've given it up.”
We'll keep the primitive simple until a real use case demands more.