Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 32

Categorization and Tagging for Content Relationships

Recommended resources

Hello! Welcome to the next lesson in our journey to build a professional accounting website with Astro.

In our last session, we brought a significant level of order to our project by structuring our component directories. This focus on clean architecture is vital for long-term maintainability. Today, we'll apply that same organizational discipline to our content.

Your goal is to build a content-driven site where blog posts, services, and case studies are easy to manage. A key part of management is organization. This lesson directly addresses that by teaching you how to extend your content schemas to establish relationships using categories and tags. By defining these relationships directly in your code, you'll make your content more structured, prevent errors, and set the stage for powerful features like filtering and creating archive pages.

1. Organizing Content with Taxonomies

Just as you use folders to organize files, websites use taxonomies to organize content. The two most common types are:

  • Categories: These are broad, often mutually exclusive groupings for your content. Think of them as the primary sections of a library. For an accounting blog, categories might be "Tax Insights," "Business Growth," or "Compliance Updates." A single blog post typically belongs to just one category.
  • Tags: These are more specific, non-hierarchical labels. They act like keywords that describe the details of the content. A post in the "Tax Insights" category might have tags like small-business, tax-deductions, and 2025-planning. A post can have many tags.

With your background in programming and data structures, you can think of the schema in src/content/config.ts as defining the "type" or "interface" for your content. Adding categories and tags is like adding new properties to that interface.


2. Implementing Categories and Tags with Zod

In Astro, we define these taxonomies within our defineCollection schema using Zod.

  • For Categories, where you have a fixed list of options, the best tool is z.enum(). This ensures that content creators can only use the categories you've defined, preventing typos and inconsistencies.
  • For Tags, where you want flexibility and to allow multiple labels, z.array(z.string()) is the perfect choice. This defines the field as an array of strings.

Let's start with a great tutorial that walks through this exact process.

Typesafe Markdown With Astro Content Collections

The article 'Typesafe Markdown With Astro Content Collections' from CodeTV provides a clear, step-by-step guide to adding fields like categories and tags to a blog collection. We'll focus on the part where the schema is defined.

Please read the section 'Step 3: Define a content collection schema', focusing on the sub-sections 'Decide what fields you want in your blog metadata' and the code block that follows. Pay close attention to how category is defined using z.enum and how tags are defined using z.array().optional().


3. Practical Application: Updating Your Schemas

Now, let's apply this to your project. We'll update the schemas for both your blog and services collections.

Step 1: Update the blog Collection

Open your src/content/config.ts file. Let's add category and tags fields to your blog collection schema. For the accounting website, we'll pre-define a few relevant categories.

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blogCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    pubDate: z.date(),
    description: z.string(),
    // ... any other fields you have
    
    // Add these new fields:
    category: z.enum(['Tax Insights', 'Business Growth', 'Compliance Updates']),
    tags: z.array(z.string()).optional(), // Tags are optional
  }),
});

// ... your other collections

export const collections = {
  blog: blogCollection,
  // ...
};

Step 2: Update a Blog Post

Now, open one of your sample blog post files (e.g., src/content/blog/my-first-post.mdx) and add the new frontmatter properties.

---
title: 'Understanding New Tax Laws for 2025'
pubDate: 2024-10-26
description: 'A brief overview of the key tax changes affecting small businesses.'
category: 'Tax Insights'
tags: ['small-business', 'tax-planning', '2025']
---

Your post content starts here...

Step 3: Test the Type Safety

This is where the power of schemas becomes obvious. In the blog post you just edited, try changing the category to something not in your enum, like category: 'General Advice'.

Your terminal running the Astro dev server will immediately show a helpful error from Zod, telling you that the value is invalid and listing the allowed options. This is the "CMS-grade safety" we're aiming for—it's now impossible to publish a post with an incorrect category.

Change the category back to a valid one, like 'Tax Insights', to resolve the error.


4. Advanced Relationships: References

Categories and tags are great for grouping, but what if you want to link different types of content? For example, what if you wanted to associate a specific author with a blog post, where the author's information (name, bio, photo) is stored in its own collection?

Simply adding an author's name as a string (author: 'John Doe') leads to data duplication. If the author's name changes, you'd have to update it in every single post.

Astro provides a more powerful solution for this: reference(). This function allows you to create a direct link from an entry in one collection to an entry in another.

Let's look at some production-ready examples to see this in action.

Astro Content Collections: Real-World schemas you can steal

The article 'Astro Content Collections: Real-World schemas you can steal' by Lexington Themes showcases several advanced schema patterns. It's a goldmine for building robust content models.

Please read the sections 'Services' and 'Properties + Agents'. In 'Services', notice the use of category with an enum, just like we implemented. In 'Properties + Agents', focus on how agent: reference("agents") is used to link a property to an agent. This demonstrates a true relational link between two different content types.

As you can see, reference() opens the door to building a truly interconnected content graph, which is essential for a scalable site. For your accounting website, you could use this to:

  • Link a service to multiple caseStudies.
  • Link a blogPost to an author.
  • Link a testimonial to the specific service it relates to.
Test your understanding!

You want to add an "author" field to your blog collection. Each author has a name, a short bio, and an avatar image. An author can write many blog posts. How would you model this in src/content/config.ts?

Show answer

The best approach would be to create a new authors collection and use a reference.

  1. Create an authors collection: Define a new collection (e.g., authors) with a schema for name, bio, and avatar. This collection would store each author's information in a single, central place.

    // In src/content/config.ts
    const authorsCollection = defineCollection({
      type: 'content', // or 'data' if the bio is short
      schema: ({ image }) => z.object({
        name: z.string(),
        bio: z.string(),
        avatar: image(),
      })
    });
    
  2. Use reference() in the blog collection: Update your blog schema to include an author field that references the authors collection.

    // In the blogCollection schema
    import { defineCollection, z, reference } from 'astro:content';
    //...
    author: reference('authors'),
    //...
    

This approach avoids data duplication and creates a robust, maintainable relationship between your content types.


Conclusion

Fantastic work! You have now enhanced your content collections with structured taxonomies, a critical step for organizing a content-heavy website. This not only improves the developer experience but also lays the foundation for building richer user-facing features.

Key Takeaways:

  • Taxonomies like categories and tags are used to organize and relate content.
  • In Astro, we define taxonomies within src/content/config.ts using Zod schemas.
  • Use z.enum() for a fixed list of categories to ensure type safety.
  • Use z.array(z.string()) for flexible, multi-value tags.
  • For more complex relationships between different content types (e.g., posts and authors), use reference() to create direct links and avoid data duplication.

Up Next:

Having these taxonomies in our frontmatter is only half the story. In the next lesson, we will bring them to life on the frontend. You'll learn how to display and link categories and tags on your blog posts and listing pages, making your content more navigable and discoverable for your users.