{"id":98042,"date":"2026-10-08T05:26:00","date_gmt":"2026-10-08T09:26:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=98042"},"modified":"2026-09-29T07:58:37","modified_gmt":"2026-09-29T11:58:37","slug":"custom-taxonomies-emdash-cms-98042","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/custom-taxonomies-emdash-cms-98042\/","title":{"rendered":"How to Build Custom Taxonomies in EmDash CMS"},"content":{"rendered":"<p><a href=\"https:\/\/emdashcms.com\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">EmDash CMS<\/a> treats taxonomies differently than WordPress. Instead of a freeform system where you create categories and tags as loose terms, EmDash makes every taxonomy part of a content type&#8217;s schema. You define the structure first, then assign content to it. That shift changes how you organize content\u2014and it eliminates the mess of orphaned terms and inconsistent tagging that plagues most WordPress sites after a few years.<\/p>\n<p>This guide walks through creating custom taxonomies in EmDash, explains why the schema-first approach wins for data integrity, and addresses the one real objection: &#8220;But WordPress taxonomies are simpler for non-technical users.&#8221;<\/p>\n<h2>What Makes EmDash Taxonomies Different<\/h2>\n<p>WordPress has two built-in taxonomies: categories and tags. You can register custom taxonomies via <code>register_taxonomy()<\/code>codecodecode in PHP. Terms are stored in a separate table (<code>wp_terms<\/code>codecodecode) and linked to posts via <code>wp_term_relationships<\/code>codecodecode. There&#8217;s no enforced schema\u2014a term is just a name and slug. You rely on plugins like Advanced Custom Fields to add metadata to terms.<\/p>\n<p>EmDash inverts this model. Every &#8220;taxonomy&#8221; is actually a field on a content type. You create a content type (e.g., &#8220;Product&#8221;), then add a field of type &#8220;Select&#8221; or &#8220;Relationship&#8221; that points to another content type (e.g., &#8220;Category&#8221;). The category itself is a content type with its own fields\u2014description, image, SEO meta. This means:<\/p>\n<ul>\n<li><strong>Terms are structured content.<\/strong> A category can have a featured image, a custom URL pattern, or a hierarchical parent-child relationship natively.<\/li>\n<li><strong>No separate taxonomy table.<\/strong> Data lives in the same storage layer as every other content type. Exports, imports, and backups treat everything consistently.<\/li>\n<li><strong>Validation at the schema level.<\/strong> You cannot assign a term that doesn&#8217;t exist. No orphaned references.<\/li>\n<\/ul>\n<p>The built-in &#8220;Categories&#8221; and &#8220;Tags&#8221; content types in EmDash are pre-configured examples of this pattern. You can use them as-is or create your own.<\/p>\n<h2>Creating a Custom Taxonomy Step by Step<\/h2>\n<p>This process assumes you have EmDash running locally or on Cloudflare. The admin interface is at <code>\/admin<\/code>codecodecode.<\/p>\n<h3>Step 1: Create the Term Content Type<\/h3>\n<p>Navigate to <strong>Admin \u2192 Content Types<\/strong> and click <strong>Create new content type<\/strong>. Name it &#8220;Category&#8221; or whatever your taxonomy is\u2014&#8221;Department,&#8221; &#8220;Genre,&#8221; &#8220;Region.&#8221; EmDash automatically generates a plural label and slug.<\/p>\n<p>Add fields that define the term. At minimum:<\/p>\n<ul>\n<li><strong>Title<\/strong> (auto-generated)<\/li>\n<li><strong>Slug<\/strong> (auto-generated from title)<\/li>\n<li><strong>Description<\/strong> (rich text or plain text)<\/li>\n<li><strong>Featured Image<\/strong> (media field)<\/li>\n<li><strong>Parent Category<\/strong> (relationship field, pointing to the same content type) \u2014 for hierarchy<\/li>\n<\/ul>\n<p>Save the content type. You now have a structured term repository.<\/p>\n<h3>Step 2: Add the Taxonomy Field to Your Main Content Type<\/h3>\n<p>If your main content type is &#8220;Post&#8221; or &#8220;Project&#8221; or &#8220;Product,&#8221; go to <strong>Admin \u2192 Content Types<\/strong>, edit that content type, and add a new field.<\/p>\n<p>Choose a field type that suits your taxonomy:<\/p>\n<ul>\n<li><strong>Select (single)<\/strong> \u2014 for a one-to-one relationship (e.g., every post belongs to exactly one department).<\/li>\n<li><strong>Relationship (many)<\/strong> \u2014 for many-to-many (e.g., a post can have multiple tags).<\/li>\n<li><strong>Checkboxes<\/strong> \u2014 for a fixed list of predefined options.<\/li>\n<\/ul>\n<p>Configure the field to reference the &#8220;Category&#8221; content type you created. You can also limit the selection to a specific subset by adding a filter condition.<\/p>\n<h3>Step 3: Populate Terms<\/h3>\n<p>Go to the content type for your terms (e.g., &#8220;Categories&#8221;) and create entries. Each entry is a full content item: title, description, image, parent. You can manage them just like posts.<\/p>\n<h3>Step 4: Assign Terms to Content<\/h3>\n<p>When editing a post or project, the taxonomy field appears as a select or relationship picker. Select the terms you want. Because the terms are content types, you can also use EmDash&#8217;s API or MCP server to assign them programmatically.<\/p>\n<h2>Using EmDash&#8217;s Built-in Categories and Tags<\/h2>\n<p>EmDash ships with two content types: &#8220;Categories&#8221; and &#8220;Tags.&#8221; They work out of the box. You can customize their fields, rename them, or delete them if they don&#8217;t fit your model.<\/p>\n<p>The key advantage: if you need a hierarchical taxonomy like WordPress categories, you add a &#8220;Parent&#8221; field (relationship to self) on the Category content type. If you need a flat taxonomy like tags, you leave it without hierarchy. Both are content types\u2014no separate code required.<\/p>\n<h2>The Defensible Position: Schema-First Taxonomies<\/h2>\n<p>Here&#8217;s the position I&#8217;ll defend: <strong>EmDash&#8217;s schema-first approach to taxonomies is superior to WordPress&#8217;s freeform system for any site that will be maintained for more than six months or will have multiple content authors.<\/strong><\/p>\n<p>In WordPress, categories and tags are easy to create\u2014any author can type a new tag on the fly. That convenience is also the problem. Over time, you accumulate:<\/p>\n<ul>\n<li>Misspelled tags (&#8220;WordPress,&#8221; &#8220;wordpress,&#8221; &#8220;wp&#8221;)<\/li>\n<li>Duplicate categories (&#8220;News,&#8221; &#8220;Latest News,&#8221; &#8220;Breaking News&#8221;)<\/li>\n<li>Orphaned terms after posts are deleted<\/li>\n<li>Inconsistent formatting (&#8220;SEO,&#8221; &#8220;seo,&#8221; &#8220;Search Engine Optimization&#8221;)<\/li>\n<\/ul>\n<p>Fixing this mess requires migration scripts or manual cleanup. In most agency migration projects from WordPress, taxonomies often accumulate thousands of orphaned terms that require manual cleanup. I&#8217;ve seen projects where 40% of tags had zero posts attached. EmDash prevents this at the architectural level. You define the terms upfront. Authors cannot create new terms on the fly\u2014they must pick from the curated list. That enforces consistency without requiring discipline.<\/p>\n<h3>The Strongest Counterargument<\/h3>\n<p>Critics will say: &#8220;WordPress&#8217;s freeform taxonomy creation is simpler for non-technical users. A content author shouldn&#8217;t need to ask a developer to create a new tag. That kills editorial agility.&#8221;<\/p>\n<p>It&#8217;s a fair point. In WordPress, an editor can type &#8220;NewProduct&#8221; as a tag and it appears instantly. In EmDash, someone with the appropriate role must create a new term content item first.<\/p>\n<p><strong>Why that argument fails for any serious content operation:<\/strong> The editorial agility gained by letting anyone create terms is outweighed by the data quality cost. Every tag or category is a structural element of your site. Terms drive navigation, filters, related content queries, and SEO metadata. A misspelled or duplicate term breaks all of those. The 30 seconds it takes to create a new category in EmDash is a trivial overhead compared to the hours of cleanup later. Moreover, EmDash&#8217;s admin is not locked to developers. You can grant &#8220;Contributor&#8221; or &#8220;Author&#8221; roles the ability to create new term entries. The process is a few clicks\u2014not a code change. The real friction is cultural, not technical.<\/p>\n<h2>Managing Taxonomies via Code or Agents<\/h2>\n<p>EmDash is built for programmatic management. If you prefer to define taxonomies in code rather than the admin UI, you can:<\/p>\n<ol>\n<li>Create a content type definition in your Astro configuration file (e.g., <code>astro.config.mjs<\/code>codecodecode).<\/li>\n<li>Define fields and relationships there.<\/li>\n<li>Deploy the changes. The content type appears in the admin automatically.<\/li>\n<\/ol>\n<p>The built-in MCP server means AI coding agents can create or modify taxonomies on your behalf. You can say: &#8220;Add a &#8216;Region&#8217; content type with fields for name, slug, and a parent region relationship.&#8221; The agent reads the skills file and executes the change.<\/p>\n<h2>Best Practices for Custom Taxonomies in EmDash<\/h2>\n<ul>\n<li><strong>Use content types, not loose fields.<\/strong> If you need a &#8220;Department&#8221; taxonomy, create a Department content type, not a text field on posts. This keeps data normalized.<\/li>\n<li><strong>Add a description field.<\/strong> Even for simple tags, a brief description helps authors choose correctly.<\/li>\n<li><strong>Set up hierarchical relationships explicitly.<\/strong> Use a self-referencing relationship field for parent-child taxonomies.<\/li>\n<li><strong>Limit term creation to editors or above.<\/strong> Use user roles to control who can create new term entries.<\/li>\n<li><strong>Plan for portability.<\/strong> EmDash stores taxonomies as structured JSON. If you ever migrate to another system, the data is self-documenting and machine-readable.<\/li>\n<\/ul>\n<h2>Closing: The Shift Toward Structured Taxonomies<\/h2>\n<p>EmDash&#8217;s taxonomy model is not just a technical <a href=\"https:\/\/overcentral.com\/en\/ichra-choice-arrangements-label-97925\/\" title=\"ICHRA Gets CHOICE Arrangements Label from CMS, SBA\" data-iacss-internal=\"1\">choice<\/a>aa\u2014it&#8217;s a philosophy. Content is not a dumping ground of unstructured text and arbitrary tags. Every piece of metadata has a defined shape. As <a href=\"https:\/\/overcentral.com\/en\/rogue-ai-agents-liability-vacuum-97898\/\" title=\"Rogue AI agents expose liability vacuum as OpenAI faces claims\" data-iacss-internal=\"1\">AI agents<\/a> become the primary content managers (writing posts, generating summaries, organizing archives), structured taxonomies are the difference between a system that can be automated and one that requires constant human oversight. EmDash taxonomies are built for that future. The current version (0.1.0) already supports the core patterns; the missing pieces are tooling and community plugins, not the architecture itself.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>EmDash CMS treats taxonomies differently than WordPress. Instead of a freeform system where you create categories and tags as loose terms, EmDash makes every taxonomy part of a content type&#8217;s schema. You define the structure first, then assign content to it. That shift changes how you organize content\u2014and it eliminates the mess of orphaned terms [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":99686,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98042.png","fifu_image_alt":"How to Build Custom Taxonomies in EmDash CMS","footnotes":""},"categories":[31],"tags":[],"class_list":["post-98042","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98042.png","fifu_image_alt":"How to Build Custom Taxonomies in EmDash CMS","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98042","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/comments?post=98042"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98042\/revisions"}],"predecessor-version":[{"id":99687,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98042\/revisions\/99687"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/99686"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=98042"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=98042"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=98042"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}