Custom Post Types & Taxonomies
Overview
ValPress provides a robust system for extending content beyond standard posts and pages using Custom Post Types (CPTs). Complementing this, Custom Taxonomies allow for sophisticated content classification through hierarchical (categories) and non-hierarchical (tags) structures. This dual system enables developers to transform ValPress from a simple blog into a full-scale content management engine capable of handling portfolios, e-commerce products, or directories.
Why It Matters
- Content Organization: CPTs isolate different data types (e.g., "Books" vs. "Events"), ensuring the admin interface remains clean and intuitive.
- Customized Metadata: Each post type can support specific features like thumbnails, excerpts, or custom editors, allowing for tailored data entry.
- Improved SEO & UX: Unique URL structures (permalinks) for each CPT help search engines understand your site's hierarchy and improve user navigation.
- Scalability: By using taxonomies, you can relate disparate content types—for example, a "Project" CPT and a "Service" CPT might both share a "Technology" taxonomy.
How It Works
ValPress manages CPTs and Taxonomies through two primary mechanisms:
- Registry System: Post types are registered into a central
PostTyperegistry during thevalpress_inithook. This registration defines labels, menu icons, and supported features (title, editor, thumbnail, etc.). - Database Storage:
- Post Types: Definitions are stored in the
post_typestable for persistent, UI-generated types. - Posts: All content, regardless of type, is stored in the
poststable with a correspondingpost_typestring column. - Taxonomies: Terms are stored in the
categories(hierarchical) andtags(flat) tables. Each term is linked to a post type by an ID or via a mapping system that allows a single taxonomy to span multiple post types.
- Post Types: Definitions are stored in the
The system uses Laravel's Eloquent models (App\Models\Post, App\Models\PostType) and a helper function register_post_type() to bridge the gap between
static code definitions and dynamic database storage.
Usage
1. Manual CPT Registration
Developers can register post types within a plugin’s plugin.php or a theme’s functions.php. It is best practice to wrap this in the valpress_init hook.
add_action('valpress_init', function () {
register_post_type('portfolio', [
'labels' => [
'name' => 'Portfolios',
'singular_name' => 'Portfolio',
],
'public' => true,
'has_archive' => true,
'menu_icon' => 'bi-briefcase',
'menu_order' => 20,
'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
]);
});
2. Dynamic CPT Registration (Admin UI)
Users with administrative privileges can create CPTs through the Custom Post Types menu in the admin dashboard:
- Navigate to Admin > Custom Post Types.
- Click Add New.
- Provide the slug (e.g.,
event), labels, and select the features to support. - Once saved, the new CPT appears in the sidebar automatically.
3. Enabling Taxonomies for CPTs
By default, new CPTs do not have categories or tags enabled. You can enable them via filters:
// Enable categories for the 'portfolio' CPT
add_filter('valpress_categories_enabled', function ($enabled, $post_type) {
if ($post_type === 'portfolio') {
return true;
}
return $enabled;
}, 10, 2);
Code Examples
Realistic Portfolio CPT with Custom URL Structure
Registering a portfolio CPT with a custom icon and support for a featured image.
/**
* Register the 'Portfolio' post type.
*/
add_action('valpress_init', function () {
$args = [
'labels' => [
'name' => __('Portfolios'),
'singular_name' => __('Portfolio'),
],
'public' => true,
'show_in_menu' => true,
'menu_icon' => 'bi-grid-3x3-gap',
'menu_order' => 10,
'has_archive' => true,
'rewrite' => ['slug' => 'work'], // Access via site.com/work/post-slug
'supports' => [
'title',
'editor',
'thumbnail',
'excerpt',
'author',
],
];
register_post_type('portfolio', $args);
});
Querying CPT Content in Blade
Using Laravel's Eloquent to fetch custom post types in your theme's archive template.
@php
$portfolios = \App\Models\Post::where('post_type', 'portfolio')
->where('status', 'publish')
->orderBy('created_at', 'desc')
->paginate(12);
@endphp
<div class="portfolio-grid">
@foreach($portfolios as $item)
<article class="portfolio-item">
<a href="{{ valpress_get_permalink($item) }}">
{!! the_featured_image($item, 'medium', ['class' => 'img-fluid']) !!}
<h3>{{ $item->title }}</h3>
</a>
<p>{{ valpress_get_post_excerpt($item) }}</p>
</article>
@endforeach
</div>
{{ $portfolios->links() }}
Advanced Usage
Registering CPT-Specific Routes
If your CPT needs a highly customized frontend layout beyond the standard template hierarchy, you can register specific routes in your plugin:
// In your plugin's routes/web.php
Route::get('/gallery/{slug}', [GalleryController::class, 'show'])
->name('gallery.show');
Extending the Admin Query
You can modify how CPTs are retrieved or displayed in the admin list using the valpress_pre_get_posts action (conceptually similar to WP) to filter by custom
metadata.
Best Practices
- Unique Slugs: Always prefix your CPT slugs (e.g.,
acme_product) to avoid collisions with core types or other plugins. - Support Features: Only enable the features you need (e.g., if a post type doesn't need an editor, don't include it in the
supportsarray). - Internationalization: Always use
__()for label strings to ensure your CPT can be translated. - Flush Permalinks: After registering a new CPT via code, you may need to clear the route cache (
php artisan route:cache) for the new URLs to work.
Common Mistakes
- Skipping the Hook: Registering CPTs outside of the
valpress_inithook can lead to race conditions where the CPT is not recognized during the boot sequence. - Recursive Taxonomies: Enabling both categories and tags for a post type that only needs one can confuse users and complicate the UI.
- Ignoring Core Flags: Setting
is_core => trueon a custom post type is discouraged as it may protect the type from being edited or deleted via the UI.
Summary
Custom Post Types and Taxonomies are the backbone of ValPress's extensibility. Whether through the intuitive Admin UI or the programmatic register_post_type()
function, they allow developers to create sophisticated, organized content structures that leverage the full power of the Laravel-based engine while maintaining
a familiar, WordPress-like development workflow.