Skip to content

Declarative Custom Post Types in WordPress with Acorn Post Types

Emnes
Illustration of multiple custom post types registered from a single config file

Custom post types are one of WordPress’s most useful features and one of its most tedious to register. A typical register_post_type() call for a single CPT runs 30–50 lines, most of it boilerplate labels. Multiply by 5–10 CPTs and 3–5 custom taxonomies and you have hundreds of lines of registration code scattered across functions.php or a handful of mu-plugins — and every single one is an opportunity for a typo, a forgotten label, or a missing capability.

The Acorn Post Types package, released by Roots in June 2025, replaces all of that with a single declarative config file. You describe your custom post types and taxonomies in config/post-types.php, Acorn generates sensible labels, registers the types with WordPress, and you never write register_post_type() again.

This guide covers declarative custom post types in WordPress with Acorn Post Types — the config structure, label generation, taxonomy integration, admin UI enhancements, and patterns for keeping CPT definitions organized as your project grows.

The Problem Acorn Post Types Solves

Here’s what registering a single CPT looks like in vanilla WordPress:

register_post_type('event', [
  'labels' => [
    'name' => 'Events',
    'singular_name' => 'Event',
    'add_new' => 'Add New',
    'add_new_item' => 'Add New Event',
    'edit_item' => 'Edit Event',
    'new_item' => 'New Event',
    'view_item' => 'View Event',
    'search_items' => 'Search Events',
    'not_found' => 'No events found',
    // ... 20+ more label variants
  ],
  'public' => true,
  'has_archive' => true,
  'menu_icon' => 'dashicons-calendar-alt',
  'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
  // ... and so on
]);

Most of those labels are mechanical — they just stamp the plural and singular forms. The non-boilerplate parts — public, has_archive, menu_icon, supports — are what actually vary. Acorn Post Types generates the boilerplate automatically and lets you focus on the interesting parts.

Installing Acorn Post Types

From your theme directory (Sage) or project root (Radicle):

composer require roots/acorn-post-types

Publish the config:

wp acorn vendor:publish --tag="acorn-post-types-config"

This creates config/post-types.php with a commented example structure.

Declaring a Custom Post Type

In config/post-types.php:

return [
  'event' => [
    'labels' => [
      'singular' => 'Event',
      'plural' => 'Events',
    ],
    'args' => [
      'menu_icon' => 'dashicons-calendar-alt',
      'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
      'has_archive' => true,
    ],
  ],
];

That’s it. Acorn Post Types generates every label variant (search text, “no events found” messages, admin menu entries, editor placeholders) from the singular and plural forms.

Reload any admin page. The Events CPT appears in the sidebar, with proper labels throughout the admin UI.

Adding Taxonomies

Custom taxonomies in the same config file:

return [
  'event' => [
    'labels' => ['singular' => 'Event', 'plural' => 'Events'],
    'args' => [
      'supports' => ['title', 'editor', 'thumbnail'],
    ],
    'taxonomies' => [
      'event_category' => [
        'labels' => ['singular' => 'Category', 'plural' => 'Categories'],
        'hierarchical' => true,
      ],
      'event_tag' => [
        'labels' => ['singular' => 'Tag', 'plural' => 'Tags'],
        'hierarchical' => false,
      ],
    ],
  ],
];

Taxonomies are scoped to the post type. A separate top-level 'taxonomies' key lets you attach shared taxonomies to multiple post types.

Features Acorn Post Types Adds Beyond Label Generation

Acorn Post Types wraps the Extended CPTs library under the hood, which adds several features beyond vanilla register_post_type():

Admin Column Configuration

Customize the admin list-table columns:

'args' => [
  'admin_cols' => [
    'featured_image' => ['title' => 'Photo', 'featured_image' => 'thumbnail'],
    'event_date' => ['title' => 'Date', 'meta_key' => 'event_date', 'date_format' => 'd M Y'],
    'categories' => ['taxonomy' => 'event_category'],
  ],
],

No more hooking into manage_posts_columns and manage_posts_custom_column manually. The declarative syntax handles sorting, filtering, and rendering.

Admin Filters

Add filter dropdowns above the admin list table:

'args' => [
  'admin_filters' => [
    'event_category' => ['title' => 'Category', 'taxonomy' => 'event_category'],
  ],
],

Custom Archive Queries

Set default archive sort and query args:

'args' => [
  'archive' => [
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_key' => 'event_date',
  ],
],

Site Content Overrides

Replace placeholder text in the admin, e.g., the “Enter title here” box:

'args' => [
  'enter_title_here' => 'Enter event name',
],

Label Generation Under the Hood

From 'singular' => 'Event' and 'plural' => 'Events', Acorn Post Types generates:

  • Menu name: Events
  • Name admin bar: Event
  • Add New: Add New
  • Add New Item: Add New Event
  • Edit Item: Edit Event
  • New Item: New Event
  • View Item: View Event
  • View Items: View Events
  • Search Items: Search Events
  • Not Found: No events found
  • Not Found in Trash: No events found in Trash
  • Archives: Event archives
  • Insert into item: Insert into event
  • Uploaded to this item: Uploaded to this event
  • Featured image: Featured image
  • Set featured image: Set featured image
  • Remove featured image: Remove featured image
  • Use as featured image: Use as featured image
  • … and roughly 15 more label variants.

If any generated label is wrong (irregular plurals, unusual phrasing), override it individually:

'labels' => [
  'singular' => 'Person',
  'plural' => 'People',
  'not_found' => 'Nobody matches that search',
],

Attaching Blade Templates to Custom Post Types

If you’re using Sage, frontend templates follow WordPress’s standard template hierarchy:

  • resources/views/single-event.blade.php renders a single event.
  • resources/views/archive-event.blade.php renders the event archive.
  • resources/views/taxonomy-event_category.blade.php renders category archives.

Acorn Post Types doesn’t change this — it just registers the post type so the templates apply.

Organizing Config as Your Project Grows

A single config/post-types.php stays manageable up to about 10 post types. Beyond that, the file gets unwieldy. Two common patterns:

Split by Topic

Break into multiple config files:

  • config/post-types/events.php
  • config/post-types/staff.php
  • config/post-types/resources.php

And register a loader in a service provider that merges them.

Factor into Classes

For projects with many post types and substantial per-type logic, structure each post type as a class (e.g., app/PostTypes/Event.php) with its own label, argument, and behavior methods. Register them all in ThemeServiceProvider.

Either approach keeps the declarative config clean without forcing everything into one file.

Frequently Asked Questions

Do Acorn Post Types work with Gutenberg?

Yes. Post types registered via Acorn Post Types are just WordPress post types — fully compatible with the block editor, custom blocks, and Gutenberg features.

Can I use Acorn Post Types with existing plugins that register CPTs?

Yes. Acorn Post Types only registers types you declare in its config; it doesn’t touch plugins’ CPT registrations.

What happens if the plugin is deactivated?

Your CPTs disappear from the admin, but the posts remain in the database. Reactivate Acorn Post Types and everything comes back. The underlying data is standard WordPress post data.

Can I migrate from register_post_type() to Acorn Post Types?

Yes. Copy the registration args into the config, remove the register_post_type() call, and existing posts continue to work unchanged. The post type name (slug) must match what was used before.

Does it work on multisite?

Yes. Each subsite gets the CPTs registered independently (since the config is loaded per request). For network-wide consistency, keep the config file the same across all sites via Composer.

What about custom post statuses?

Acorn Post Types focuses on post types and taxonomies. Custom post statuses can be registered separately with register_post_status() in your service provider.

Declarative Is the Right Default

Every WordPress project above trivial complexity needs custom post types and taxonomies. The mechanical boilerplate that comes with registering them by hand isn’t intellectually hard — it’s just volume, and volume means typos, inconsistency, and friction. Acorn Post Types’ declarative config collapses the boilerplate and lets you focus on the decisions that actually matter (public visibility, REST API exposure, supported features, admin columns).

For any Acorn-based project — Sage theme, Radicle project, or plain Bedrock + Acorn — adopting Acorn Post Types is a quick win. For agencies building multiple sites with similar content models, the shared config patterns scale nicely.

At Emnes, every Sage-based client project uses Acorn Post Types or a similar declarative registration pattern. The config files become concise, readable, and easy to review — a huge improvement over the hundred-line post-type registration files of the past.

Related reading: Acorn explained, Sage 11 deep dive, WordPress custom fields without code.