Taxonomies

On this page

Taxonomies are classification systems for organizing content. EmDash includes built-in categories and tags, and supports custom taxonomies for specialized classification needs.

Built-in Taxonomies

EmDash provides two default taxonomies:

TaxonomyTypeDescription
CategoriesHierarchicalNested classification with parent-child relationships
TagsFlatSimple labels without hierarchy

Both are available for the posts collection by default.

Managing Terms

Create a Term

Admin Dashboard

  1. Go to the taxonomy page (e.g., /_emdash/admin/taxonomies/category)

  2. Enter the term name in the Add New form

  3. Optionally set:

    • Slug - URL identifier (auto-generated from name)
    • Parent - For hierarchical taxonomies
    • Description - Term description
  4. Click Add

Content Editor

  1. Open a content entry in the editor

  2. Find the taxonomy panel in the sidebar

  3. For categories: check the boxes for applicable terms, or click + Add New

  4. For tags: type tag names separated by commas

  5. Save the content

API

POST /_emdash/api/taxonomies/category/terms
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN

{
  "slug": "tutorials",
  "label": "Tutorials",
  "parentId": "term_abc",
  "description": "How-to guides and tutorials"
}

Edit a Term

  1. Go to the taxonomy terms page

  2. Click Edit next to the term

  3. Update the name, slug, parent, or description

  4. Click Save

Delete a Term

  1. Go to the taxonomy terms page

  2. Click Delete next to the term

  3. Confirm the deletion

Querying Taxonomies

EmDash provides functions to query taxonomy terms and filter content by term.

Get All Terms

Retrieve all terms for a taxonomy:

import { getTaxonomyTerms } from "emdash";

// Get all categories (returns tree structure)
const categories = await getTaxonomyTerms("category");

// Get all tags (returns flat list)
const tags = await getTaxonomyTerms("tag");

For hierarchical taxonomies, terms include a children array:

interface TaxonomyTerm {
	id: string;
	name: string; // Taxonomy name ("category")
	slug: string; // Term slug ("news")
	label: string; // Display label ("News")
	parentId?: string;
	description?: string;
	children: TaxonomyTerm[];
	count?: number; // Number of entries with this term
}

Get a Single Term

import { getTerm } from "emdash";

const category = await getTerm("category", "news");
// Returns TaxonomyTerm or null

Get Terms for an Entry

import { getEntryTerms } from "emdash";

// Get all categories for a post
const categories = await getEntryTerms("posts", "post-123", "category");

// Get all tags for a post
const tags = await getEntryTerms("posts", "post-123", "tag");

Filter Content by Term

Use getEmDashCollection with the where filter:

import { getEmDashCollection } from "emdash";

// Posts in the "news" category
const { entries: newsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: "news" },
});

// Posts with the "javascript" tag
const { entries: jsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { tag: "javascript" },
});

Or use the convenience function:

import { getEntriesByTerm } from "emdash";

const newsPosts = await getEntriesByTerm("posts", "category", "news");

Building Taxonomy Pages

Category Archive

Create a page that lists posts in a category:

---
import { getTaxonomyTerms, getTerm, getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";

export async function getStaticPaths() {
  const categories = await getTaxonomyTerms("category");

  // Flatten hierarchical tree for routing
  function flatten(terms) {
    return terms.flatMap((term) => [term, ...flatten(term.children)]);
  }

  return flatten(categories).map((cat) => ({
    params: { slug: cat.slug },
    props: { category: cat },
  }));
}

const { category } = Astro.props;

const { entries: posts } = await getEmDashCollection("posts", {
  status: "published",
  where: { category: category.slug },
});
---

<Base title={category.label}>
  <h1>{category.label}</h1>
  {category.description && <p>{category.description}</p>}
  <p>{category.count} posts</p>

  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.data.slug}`}>{post.data.title}</a>
      </li>
    ))}
  </ul>
</Base>

Tag Archive

Create a page that lists posts with a tag:

---
import { getTaxonomyTerms, getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";

export async function getStaticPaths() {
  const tags = await getTaxonomyTerms("tag");

  return tags.map((tag) => ({
    params: { slug: tag.slug },
    props: { tag },
  }));
}

const { tag } = Astro.props;

const { entries: posts } = await getEmDashCollection("posts", {
  status: "published",
  where: { tag: tag.slug },
});
---

<Base title={`Posts tagged "${tag.label}"`}>
  <h1>#{tag.label}</h1>

  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.data.slug}`}>{post.data.title}</a>
      </li>
    ))}
  </ul>
</Base>

Category List Widget

Display a list of categories with post counts:

---
import { getTaxonomyTerms } from "emdash";

const categories = await getTaxonomyTerms("category");
---

<nav class="category-list">
  <h3>Categories</h3>
  <ul>
    {categories.map((cat) => (
      <li>
        <a href={`/category/${cat.slug}`}>
          {cat.label} ({cat.count})
        </a>
        {cat.children.length > 0 && (
          <ul>
            {cat.children.map((child) => (
              <li>
                <a href={`/category/${child.slug}`}>
                  {child.label} ({child.count})
                </a>
              </li>
            ))}
          </ul>
        )}
      </li>
    ))}
  </ul>
</nav>

Tag Cloud

Display tags with size based on usage:

---
import { getTaxonomyTerms } from "emdash";

const tags = await getTaxonomyTerms("tag");

// Calculate font sizes based on count
const counts = tags.map((t) => t.count ?? 0);
const maxCount = Math.max(...counts, 1);
const minSize = 0.8;
const maxSize = 2;

function getSize(count: number) {
  const ratio = count / maxCount;
  return minSize + ratio * (maxSize - minSize);
}
---

<div class="tag-cloud">
  {tags.map((tag) => (
    <a
      href={`/tag/${tag.slug}`}
      style={`font-size: ${getSize(tag.count ?? 0)}rem`}
    >
      {tag.label}
    </a>
  ))}
</div>

Displaying Terms on Content

Show categories and tags on a post:

---
import { getEntryTerms } from "emdash";

interface Props {
  collection: string;
  entryId: string;
}

const { collection, entryId } = Astro.props;

const categories = await getEntryTerms(collection, entryId, "category");
const tags = await getEntryTerms(collection, entryId, "tag");
---

<div class="post-terms">
  {categories.length > 0 && (
    <div class="categories">
      <span>Posted in:</span>
      {categories.map((cat, i) => (
        <>
          {i > 0 && ", "}
          <a href={`/category/${cat.slug}`}>{cat.label}</a>
        </>
      ))}
    </div>
  )}

  {tags.length > 0 && (
    <div class="tags">
      {tags.map((tag) => (
        <a href={`/tag/${tag.slug}`} class="tag">
          #{tag.label}
        </a>
      ))}
    </div>
  )}
</div>

Custom Taxonomies

Create taxonomies beyond categories and tags for specialized needs.

Create a Custom Taxonomy

Use the admin API to create a taxonomy:

POST /_emdash/api/taxonomies
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN

{
  "name": "genre",
  "label": "Genres",
  "labelSingular": "Genre",
  "hierarchical": true,
  "collections": ["books", "movies"]
}

Use Custom Taxonomies

Query and display custom taxonomies the same way as built-in ones:

import { getTaxonomyTerms, getEmDashCollection } from "emdash";

// Get all genres
const genres = await getTaxonomyTerms("genre");

// Get books in a genre
const { entries: sciFiBooks } = await getEmDashCollection("books", {
	where: { genre: "science-fiction" },
});

Assign to Collections

Taxonomies specify which collections they apply to:

{
  "name": "difficulty",
  "label": "Difficulty Levels",
  "hierarchical": false,
  "collections": ["recipes", "tutorials"]
}

Taxonomy API Reference

REST Endpoints

EndpointMethodDescription
/_emdash/api/taxonomiesGETList taxonomy definitions
/_emdash/api/taxonomiesPOSTCreate taxonomy
/_emdash/api/taxonomies/:name/termsGETList terms
/_emdash/api/taxonomies/:name/termsPOSTCreate term
/_emdash/api/taxonomies/:name/terms/:slugGETGet term
/_emdash/api/taxonomies/:name/terms/:slugPUTUpdate term
/_emdash/api/taxonomies/:name/terms/:slugDELETEDelete term

Assign Terms to Content

POST /_emdash/api/content/posts/post-123/terms/category
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN

{
  "termIds": ["term_news", "term_featured"]
}

Next Steps