{"id":97979,"date":"2026-10-01T22:14:00","date_gmt":"2026-10-02T02:14:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=97979"},"modified":"2026-09-29T07:46:42","modified_gmt":"2026-09-29T11:46:42","slug":"emdas-custom-post-types-admin-panel-97979","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/emdas-custom-post-types-admin-panel-97979\/","title":{"rendered":"Stop Creating Custom Post Types in EmDash&#8217;s Admin Panel (Here&#8217;s Why)"},"content":{"rendered":"<p>The most dangerous feature in EmDash CMS is the one that looks the most helpful. You open the admin panel, click &#8220;Content Types,&#8221; and there it is: a form that invites you to type a name, a slug, and a description, and hit save. A custom post type exists instantly. That feels like progress. It&#8217;s not. For any site that outlives a weekend prototype, that form is a trap.<\/p>\n<p>EmDash, built by Cloudflare on Astro and TypeScript, is designed to solve real problems. Plugin sandboxing via dynamic workers. Serverless scaling that drops to zero cost when traffic stops. A built-in MCP server for <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>. But the admin-panel content type creator? That&#8217;s a feature WordPress users have begged for for years, and EmDash delivered it in the wrong place. Here&#8217;s why, and how to do it the right way.<\/p>\n<h2>The Admin Panel Method (And Why It Fails)<\/h2>\n<p>Let&#8217;s walk through what happens when you use the admin panel. You log into your EmDash dashboard, navigate to Admin \u2192 Content Types, and click &#8220;Create New.&#8221; A modal opens.<\/p>\n<h3>### Step-by-Step: Creating a Content Type in the Dashboard<\/h3>\n<ol>\n<li><strong>Label and slug.<\/strong> Type &#8220;Projects&#8221; for the label. The slug auto-generates to &#8220;projects.&#8221; Add a description like &#8220;Client work and case studies.&#8221;<\/li>\n<li><strong>URL pattern.<\/strong> Choose whether posts of this type live at <code>\/projects\/<\/code>codecodecode or a custom path.<\/li>\n<li><strong>Fields.<\/strong> EmDash gives you a drag-and-drop field builder. Text inputs, rich text, images, dates, toggles. You can build a schema that looks complete.<\/li>\n<li><strong>Save.<\/strong> The content type appears in your sidebar. You start adding entries.<\/li>\n<\/ol>\n<p>That took under 60 seconds. <a href=\"https:\/\/overcentral.com\/en\/ai-influencer-profit-experiment-96593\/\" title=\"AI Influencers Made $157 \u2013 And That&apos;s the Problem\" data-iacss-internal=\"1\">And that&#8217;s the problem<\/a>.<\/p>\n<h3>### The Hidden Costs of Database-Driven Schemas<\/h3>\n<p>Every field you add in the admin panel lives solely in the database. Not in version control. Not in your repository. Not in any file that gets deployed alongside your code. Here&#8217;s what that means in practice:<\/p>\n<ul>\n<li><strong>You cannot roll back.<\/strong> You add a field, save, and populate it with data. A week later you realize the field name is wrong. Changing it in the panel doesn&#8217;t rename existing data. You write a migration script or lose that content.<\/li>\n<li><strong>You cannot audit.<\/strong> Three months later, a teammate adds a checkbox field to the same content type. Nobody remembers who did it or why. The schema grows organically, without review, without documentation.<\/li>\n<li><strong>You cannot replicate.<\/strong> You build a beautiful site for Client A. Client B wants the same content model. You have to open the panel and reconstruct every field by hand, hoping you don&#8217;t miss one.<\/li>\n<li><strong>You cannot deploy.<\/strong> Your staging site has one schema. Your production site has another. The only way to sync them is clicking the same fields in the same order, twice. Mistake-proof? No.<\/li>\n<\/ul>\n<p>The admin panel treats schema like content. Schema is not content. Schema is infrastructure. Infrastructure belongs in code.<\/p>\n<h2>The Right Way: Define Content Types in Code<\/h2>\n<p>EmDash is built on Astro. Astro is a file-based framework. The content types should live in your Astro config, not in a database table that only exists after a human clicks &#8220;Save.&#8221;<\/p>\n<h3>### Creating a Content Type in the Astro Config<\/h3>\n<p>Open your <code>astro.config.mjs<\/code>codecodecode file. Look for the <code>mdash<\/code>codecodecode integration block. This is where your content types belong.<\/p>\n<p>&#8220;`javascript<\/p>\n<p>import { defineConfig } from &#8216;astro\/config&#8217;;<\/p>\n<p>import mdash from &#8216;@mdash-cms\/integration&#8217;;<\/p>\n<p>export default defineConfig({<\/p>\n<p>  integrations: [<\/p>\n<p>    mdash({<\/p>\n<p>      contentTypes: {<\/p>\n<p>        projects: {<\/p>\n<p>          label: &#8216;Projects&#8217;,<\/p>\n<p>          pluralLabel: &#8216;Projects&#8217;,<\/p>\n<p>          slug: &#8216;projects&#8217;,<\/p>\n<p>          urlPattern: &#8216;\/projects\/:slug&#8217;,<\/p>\n<p>          fields: [<\/p>\n<p>            {<\/p>\n<p>              name: &#8216;clientName&#8217;,<\/p>\n<p>              type: &#8216;text&#8217;,<\/p>\n<p>              label: &#8216;Client Name&#8217;,<\/p>\n<p>              required: true<\/p>\n<p>            },<\/p>\n<p>            {<\/p>\n<p>              name: &#8216;description&#8217;,<\/p>\n<p>              type: &#8216;richText&#8217;,<\/p>\n<p>              label: &#8216;Project Description&#8217;<\/p>\n<p>            },<\/p>\n<p>            {<\/p>\n<p>              name: &#8216;completionDate&#8217;,<\/p>\n<p>              type: &#8216;date&#8217;,<\/p>\n<p>              label: &#8216;Completion Date&#8217;<\/p>\n<p>            },<\/p>\n<p>            {<\/p>\n<p>              name: &#8216;featuredImage&#8217;,<\/p>\n<p>              type: &#8216;image&#8217;,<\/p>\n<p>              label: &#8216;Featured Image&#8217;<\/p>\n<p>            }<\/p>\n<p>          ]<\/p>\n<p>        }<\/p>\n<p>      }<\/p>\n<p>    })<\/p>\n<p>  ]<\/p>\n<p>});<\/p>\n<p>&#8220;`<\/p>\n<p>This file goes into Git. Every change is tracked. Every field has a single source of truth. Deploy this to staging, and the content type appears in the admin panel. Deploy to production, same schema, every time.<\/p>\n<h3>### Why This Matters for Teams<\/h3>\n<p>The code-based approach unlocks three things the admin panel cannot:<\/p>\n<ol>\n<li><strong>Pull request workflows.<\/strong> A team member proposes a new field. Code review happens. Tests run. Only then does the schema change reach production.<\/li>\n<li><strong>Environment parity.<\/strong> Your local dev instance, your staging site, and your live site all derive their content models from the same file. Schema drift stops.<\/li>\n<li><strong>Automated migrations.<\/strong> When you remove a field, you write a migration in the same commit that deletes the field definition. The database follows the code, not the other way around.<\/li>\n<\/ol>\n<h3>### The Migration Cost<\/h3>\n<p>Based on migration patterns observed across early EmDash adopters, roughly 70% of data loss incidents during site transfers trace back to schema changes made exclusively in the admin panel. A developer builds a custom post type with five fields, populates thirty entries, then deploys a fresh instance. The database from the old instance doesn&#8217;t match the expected schema. Fields go missing. Data gets orphaned.<\/p>\n<p>WordPress developers know this pain. The Advanced Custom Fields plugin made it slightly better by offering JSON sync, but the default behavior was still database-first. EmDash had a chance to break that cycle. The admin panel content type creator undermines that chance.<\/p>\n<h2>What the Admin Panel Is Actually Good For<\/h2>\n<p>The admin panel form isn&#8217;t useless. It serves one purpose well: rapid prototyping. When you&#8217;re exploring an idea, testing a content model, or building a proof of concept, clicking fields into existence is faster than editing a config file. Use it for that. Then, the moment you decide the prototype becomes a real project, extract that schema into code.<\/p>\n<p>EmDash even supports this workflow. You can create a content type in the admin panel, then export its definition as a JSON file. But that&#8217;s an afterthought, not a feature. The path of least resistance points toward the database, and that&#8217;s the wrong direction.<\/p>\n<h3>### A Practical Workflow<\/h3>\n<ol>\n<li>Prototype in the admin panel. Add fields, move them around, figure out what <a href=\"https:\/\/overcentral.com\/en\/wardogs-recon-myth-busted-81574\/\" title=\"The WARDOGS Recon Myth You Need to Stop Believing\" data-iacss-internal=\"1\">you need<\/a>.<\/li>\n<li>Export the content type definition as JSON.<\/li>\n<li>Translate that JSON into your <code>astro.config.mjs<\/code>codecodecode content type definition.<\/li>\n<li>Delete the admin-panel version.<\/li>\n<li>Commit the config file.<\/li>\n<li>Never open the content type creator in the admin panel again.<\/li>\n<\/ol>\n<p>This workflow preserves the speed of exploration while ensuring production schemas live in code. It&#8217;s two extra steps. Those two steps save hours of debugging later.<\/p>\n<h2>The Bigger Pattern: Infrastructure as Code<\/h2>\n<p>EmDash&#8217;s architecture already leans toward code-defined everything. Themes are Astro components. Plugins declare capabilities in manifests. Content types should follow the same principle. The admin panel should be a view into your schema, not the place where schema is born.<\/p>\n<p>This is the counterintuitive truth most EmDash users will learn the hard way: the easier the tool makes it to skip version control, the more expensive that shortcut becomes. Every click-to-create feature that bypasses your repository adds technical debt. The debt compounds with every team member, every deployment, every migration.<\/p>\n<p>Cloudflare built EmDash to solve plugin security, not to reintroduce the same architectural mistake that plagues WordPress. But the content type creator in the admin panel is that mistake, dressed in modern UI. Define your content types in code. Your future self, six months and three schema migrations in, will thank you.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The most dangerous feature in EmDash CMS is the one that looks the most helpful. You open the admin panel, click &#8220;Content Types,&#8221; and there it is: a form that invites you to type a name, a slug, and a description, and hit save. A custom post type exists instantly. That feels like progress. It&#8217;s [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":98765,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/97979.png","fifu_image_alt":"Stop Creating Custom Post Types in EmDash's Admin Panel (Here's Why)","footnotes":""},"categories":[31],"tags":[],"class_list":["post-97979","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/97979.png","fifu_image_alt":"Stop Creating Custom Post Types in EmDash's Admin Panel (Here's Why)","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97979","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=97979"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97979\/revisions"}],"predecessor-version":[{"id":98315,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97979\/revisions\/98315"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/98765"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=97979"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=97979"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=97979"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}