Taxonomy

Classify content with taxonomies like tags and categories.

Taxonomies let you classify content using frontmatter fields like tags or categories. A taxonomy is a classification system where each unique value is called a term. For example, a "tags" taxonomy might have the terms "dart", "flutter", and "jaspr".

TaxonomyAggregator is a built-in PagesAggregator that scans loaded content pages, extracts all unique terms from a frontmatter field, and generates routes for each one. See Pages Aggregator for the general aggregation concept.

Setup

Add a TaxonomyAggregator to ContentApp.custom() via the pagesAggregators parameter:

ContentApp.custom(
  eagerlyLoadAllPages: true,
  loaders: [FilesystemLoader('content')],
  pagesAggregators: [
    TaxonomyAggregator(
      taxonomy: 'tags',
      taxonomyPageBuilder: (context, taxonomy) => TagsPage(),
      termPageBuilder: (context, taxonomy, term) => TagPage(term: term),
    ),
  ],
  configResolver: PageConfig.all(
    // ... your config
  ),
);

Eager loading must be enabled (eagerlyLoadAllPages: true) when using aggregators, as they depend on all content pages being loaded before generating routes.

Term Normalization

All terms are normalized before use. The normalization converts values to lowercase and replaces spaces with dashes:

  • "Dart"dart
  • "My Tag"my-tag
  • "Hello World"hello-world

This means tags: [Dart] and tags: [dart] in different files refer to the same term.

Querying Taxonomy Data

The TaxonomyContext extension on BuildContext provides methods to query taxonomy data from within component builders.

These methods are server-only. They throw an UnsupportedError when called on the client. Wrap calls with a !kIsWeb check or use them only in server-rendered components.

taxonomyTermRefs

Returns all term refs for a given taxonomy. The taxonomy index ref is excluded.

final termRefs = context.taxonomyTermRefs('tags');
// Returns refs for /tags/dart, /tags/flutter, etc.

taxonomyTermRef

Returns the term ref for a specific taxonomy and term, or null if no such ref exists. The term is normalized before matching, so both 'Dart' and 'dart' will find the same ref.

final dartRef = context.taxonomyTermRef('tags', 'Dart');

if (dartRef != null) {
  return a(href: dartRef.url, [text('Dart')]);
}

taxonomyIndexRef

Returns the index ref for a given taxonomy, or null if no index route was generated.

final tagsIndexRef = context.taxonomyIndexRef('tags');

pagesForTerm

Returns all content pages that have the given term in their frontmatter taxonomy field.

final dartPosts = context.pagesForTerm('tags', 'dart');

return ul([
  for (final post in dartPosts)
    li([a(href: post.url, [text(post.data.page['title'] as String)])]),
]);

taxonomyTermRefsWithCount

Returns a map of term refs to their content page counts.

final tagCloud = context.taxonomyTermRefsWithCount('tags');
for (final MapEntry(:key, :value) in tagCloud.entries) {
  // key.term == 'dart', 'flutter', etc.
  // key.url == '/tags/dart', '/tags/flutter', etc.
  // value == number of content pages with this tag
}

Accessing Route Info

Inside a route builder, use context.currentRouteInfo<T>() to access the TaxonomyTermRouteInfo or TaxonomyRouteInfo for the current route:

termPageBuilder: (context, taxonomy, term) {
  final info = context.currentRouteInfo<TaxonomyTermRouteInfo>();
  // info.term == normalized term string
  // info.url == route URL
  // info.data == data from initialTermDataBuilder
  return TagPage(term: term);
},

Properties

Properties

taxonomyStringrequired

The frontmatter key to extract terms from (e.g., 'tags', 'categories').

termPageBuilderTaxonomyTermPageBuilderrequired

Builder function for individual term routes. Receives the BuildContext, the taxonomy name, and the normalized term string.

termPageBuilder: (context, taxonomy, term) => TagPage(term: term),
taxonomyPageBuilderTaxonomyPageBuilder?

Optional builder for the taxonomy index route. Receives the BuildContext and the taxonomy name. If not provided, no index route is generated.

taxonomyPageBuilder: (context, taxonomy) => TagsPage(),
taxonomySlugString?

The URL slug for the taxonomy index route. Defaults to the taxonomy name. For example, with taxonomy: 'tags' and taxonomySlug: 'labels', the index route is generated at /labels instead of /tags.

termSlugString?

The URL slug for individual term routes. Defaults to the taxonomy name. For example, with taxonomy: 'tags' and termSlug: 'tag', term routes are generated at /tag/dart instead of /tags/dart.

initialTermDataBuilderTaxonomyTermInitialDataBuilder?

Optional function to provide additional data for each term route. Receives the taxonomy name and the normalized term string. The returned map is stored in TaxonomyTermRouteInfo.data and accessible via context.currentRouteInfo<TaxonomyTermRouteInfo>().

initialTermDataBuilder: (taxonomy, term) => {'title': 'Posts tagged: $term'},
taxonomyInitialDataBuilderTaxonomyInitialDataBuilder?

Optional function to provide additional data for the taxonomy index route. Receives the taxonomy name. The returned map is stored in TaxonomyRouteInfo.data and accessible via context.currentRouteInfo<TaxonomyRouteInfo>().

taxonomyInitialDataBuilder: (taxonomy) => {'title': 'All $taxonomy'},
supportedExtensionsList<String>

File extensions to scan for frontmatter. Defaults to ['.md', '.html'].

URL Customization

By default, generated routes use the taxonomy name as the URL prefix. Use taxonomySlug and termSlug to customize the URL structure:

TaxonomyAggregator(
  taxonomy: 'tags',
  taxonomySlug: 'labels',  // Index route at /labels instead of /tags
  termSlug: 'label',       // Term routes at /label/dart instead of /tags/dart
  // ...
)

Multiple Taxonomies

You can use multiple TaxonomyAggregators for different classification systems:

ContentApp.custom(
  eagerlyLoadAllPages: true,
  loaders: [FilesystemLoader('content')],
  pagesAggregators: [
    TaxonomyAggregator(
      taxonomy: 'tags',
      termPageBuilder: (context, taxonomy, term) { /* ... */ },
    ),
    TaxonomyAggregator(
      taxonomy: 'categories',
      termPageBuilder: (context, taxonomy, term) { /* ... */ },
    ),
  ],
  // ...
);

Each taxonomy operates independently — terms from one taxonomy do not interfere with another.

Full Example

TagsPage

The TagsPage component renders the taxonomy index page at /tags. It uses taxonomyTermRefsWithCount() to display all tags as a tag cloud.

class TagsPage extends StatelessComponent {
  @override
  Component build(BuildContext context) {
    final tagCloud = context.taxonomyTermRefsWithCount('tags');
    return div(classes: 'tag-cloud', [
      for (final MapEntry(:key, :value) in tagCloud.entries)
        a(href: key.url, [text('${key.term} ($value)')]),
    ]);
  }
}

TagPage

The TagPage component renders an individual term page at /tags/{term}. It uses pagesForTerm() to find all content pages with that tag.

class TagPage extends StatelessComponent {
  const TagPage({required this.term});

  final String term;

  @override
  Component build(BuildContext context) {
    final posts = context.pagesForTerm('tags', term);
    return div([
      h1([text('Posts tagged: $term')]),
      ul([
        for (final post in posts)
          li([a(href: post.url, [text(post.data.page['title'] as String)])]),
      ]),
    ]);
  }
}