---
title: Taxonomies
description: Organize content with categories, tags, and custom taxonomies.
---

import { Aside, Steps, Tabs, TabItem } from "@astrojs/starlight/components";

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:

| Taxonomy       | Type         | Description                                           |
| -------------- | ------------ | ----------------------------------------------------- |
| **Categories** | Hierarchical | Nested classification with parent-child relationships |
| **Tags**       | Flat         | Simple labels without hierarchy                       |

Both are available for the posts collection by default.

## Managing Terms

### Create a Term

<Tabs>
  <TabItem label="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**

  </TabItem>
  <TabItem label="Content Editor">

5. Open a content entry in the editor

6. Find the taxonomy panel in the sidebar

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

8. For tags: type tag names separated by commas

9. Save the content

  </TabItem>
  <TabItem label="API">

```bash
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"
}
```

  </TabItem>
</Tabs>

### 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

<Aside type="caution">
	Deleting a term removes it from all associated content. Content is not deleted, only the term
	assignment.
</Aside>

## Querying Taxonomies

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

### Get All Terms

Retrieve all terms for a taxonomy:

```ts
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:

```ts
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

```ts
import { getTerm } from "emdash";

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

### Get Terms for an Entry

```ts
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:

```ts
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:

```ts
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:

```astro title="src/pages/category/[slug].astro"
---
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:

```astro title="src/pages/tag/[slug].astro"
---
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:

```astro title="src/components/CategoryList.astro"
---
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:

```astro title="src/components/TagCloud.astro"
---
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:

```astro title="src/components/PostTerms.astro"
---
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:

```bash
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:

```ts
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:

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

## Taxonomy API Reference

### REST Endpoints

| Endpoint                                      | Method | Description               |
| --------------------------------------------- | ------ | ------------------------- |
| `/_emdash/api/taxonomies`                   | GET    | List taxonomy definitions |
| `/_emdash/api/taxonomies`                   | POST   | Create taxonomy           |
| `/_emdash/api/taxonomies/:name/terms`       | GET    | List terms                |
| `/_emdash/api/taxonomies/:name/terms`       | POST   | Create term               |
| `/_emdash/api/taxonomies/:name/terms/:slug` | GET    | Get term                  |
| `/_emdash/api/taxonomies/:name/terms/:slug` | PUT    | Update term               |
| `/_emdash/api/taxonomies/:name/terms/:slug` | DELETE | Delete term               |

### Assign Terms to Content

```bash
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

- [Create a Blog](/guides/create-a-blog/) - Use categories and tags in a blog
- [Querying Content](/guides/querying-content/) - Filter by taxonomy terms
- [Working with Content](/guides/working-with-content/) - Assign terms in the editor
