Overview
Content types allow you to define custom post types in Digital Shopfront CMS. This guide covers how to register content types programmatically, configure their behavior, and integrate them with the admin interface.
Table of Contents
- Registering Content Types
- Available Options
- Labels
- Table Naming Conventions
- Admin Pages Configuration
- Best Practices
- Examples
Registering Content Types
Via Service Provider
Register content types in your service provider's boot() method:
<?php
namespace App\Providers;
use ArtisanPackUI\CMSFramework\Modules\ContentTypes\Managers\ContentTypeManager;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
$contentTypeManager = app(ContentTypeManager::class);
$contentTypeManager->register([
'name' => 'Products',
'slug' => 'products',
'table_name' => 'products',
'model_class' => 'App\\Models\\Product',
'description' => 'Manage your product catalog',
'hierarchical' => false,
'has_archive' => true,
'archive_slug' => 'shop',
'supports' => ['title', 'content', 'excerpt', 'featured_image'],
'public' => true,
'show_in_admin' => true,
'icon' => 'fas-shopping-cart',
'menu_position' => 30,
'admin_pages' => [
'listing' => [
'enabled' => true,
'title' => 'Products',
'slug' => 'products',
'component' => 'Modules\\Shop\\Livewire\\Products\\Index',
'capability' => 'products.view',
'icon' => 'fas-shopping-cart',
'position' => 30,
],
'create' => [
'enabled' => true,
'title' => 'Add New Product',
'slug' => 'products.create',
'component' => 'Modules\\Shop\\Livewire\\Products\\Create',
'capability' => 'products.create',
'parent_slug' => 'products',
],
'edit' => [
'enabled' => true,
'title' => 'Edit Product',
'slug' => 'products.edit',
'component' => 'Modules\\Shop\\Livewire\\Products\\Edit',
'capability' => 'products.edit',
'parent_slug' => 'products',
'hidden' => true,
],
],
]);
}
}
Via Filter Hook
Register content types using the filter hook (useful for plugins):
addFilter('ap.contentTypes.registeredContentTypes', function ($contentTypes) {
$contentTypes['products'] = [
'name' => 'Products',
'slug' => 'products',
'table_name' => 'products',
'model_class' => 'App\\Models\\Product',
'supports' => ['title', 'content', 'featured_image'],
'has_archive' => true,
'archive_slug' => 'shop',
'public' => true,
'show_in_admin' => true,
];
return $contentTypes;
});
Via Database
Create content types through the admin UI or via API:
$contentType = ContentType::create([
'name' => 'Events',
'slug' => 'events',
'table_name' => 'events',
'model_class' => 'App\\Models\\Event',
'hierarchical' => false,
'has_archive' => true,
'public' => true,
'show_in_admin' => true,
]);
Available Options
Required Options
| Option | Type | Description |
|---|---|---|
name |
string | Display name, conventionally the plural label (e.g., "Blog Posts") |
slug |
string | Machine name (e.g., "posts") |
table_name |
string | Database table name (e.g., "posts") |
model_class |
string | Fully qualified model class |
Optional Options
| Option | Type | Default | Description |
|---|---|---|---|
singular_name |
string | null | 2.13.0. Singular label (e.g., "Product"). See Labels |
plural_name |
string | null | 2.13.0. Plural label (e.g., "Products"). See Labels |
labels |
array | null | 2.13.0. Label overrides keyed by label name. See Labels |
description |
string | null | Description shown in admin |
hierarchical |
boolean | false | Supports parent-child relationships |
has_archive |
boolean | true | Has archive pages |
archive_slug |
string | null | Archive URL slug (e.g., "blog") |
supports |
array | null | Supported features |
metadata |
array | null | Additional custom settings |
public |
boolean | true | Visible on frontend |
show_in_admin |
boolean | true | Show in admin menu |
icon |
string | null | FontAwesome icon (e.g., "fas-newspaper") |
menu_position |
integer | null | Admin menu position |
admin_pages |
array | null | Admin page configurations |
Supports Options
The supports array defines which features are available:
'supports' => [
'title', // Title field
'content', // Content editor
'excerpt', // Excerpt field
'featured_image', // Featured image
'author', // Author assignment
'comments', // Comments (if implemented)
'revisions', // Revisions (if implemented)
]
Labels
Since 2.13.0. A content type carries a WordPress-style label set so the
admin can say "Add New Product" and "Edit Product" instead of reusing name
everywhere.
$contentTypeManager->register([
'name' => 'Products',
'singular_name' => 'Product',
'plural_name' => 'Products',
'labels' => [
'add_new_item' => 'Add a Product',
],
'slug' => 'products',
// ...
]);
$contentType = $contentTypeManager->getContentType('products');
$contentType->getSingularLabel(); // "Product"
$contentType->getPluralLabel(); // "Products"
$contentType->getLabel('edit_item'); // "Edit Product"
$contentType->getLabel('add_new_item'); // "Add a Product" ( override )
$contentType->getLabels(); // the full set below
Fallbacks
singular_name and plural_name are optional:
- Singular falls back to
Str::singular( name ). - Plural falls back to
Str::plural( singular_name )when a singular is set, otherwiseStr::plural( name ).
The inflector leaves a word unchanged when it is already in the requested
form, so a plural name ( "Pages", "Blog Posts" ) and a singular one
( "Package" ) both resolve correctly. It can guess wrong for some words, e.g.
"Media" singularizes to "Medium" and "FAQ" pluralizes to "FAQS". Set the
labels explicitly when that matters.
The 2.13.0 migration backfills singular_name and plural_name for existing
rows with these same rules. Run php artisan migrate after upgrading.
Label keys
| Key | Default |
|---|---|
singular_name |
singular label |
plural_name |
plural label |
menu_name |
plural label |
add_new |
"Add New" |
add_new_item |
"Add New {singular}" |
new_item |
"New {singular}" |
edit_item |
"Edit {singular}" |
view_item |
"View {singular}" |
view_items |
"View {plural}" |
all_items |
"All {plural}" |
search_items |
"Search {plural}" |
not_found |
"No {plural} found." |
not_found_in_trash |
"No {plural} found in Trash." |
parent_item_colon |
"Parent {singular}:" |
archives |
"{singular} Archives" |
Defaults are translatable ( __() ). A non-empty string in labels replaces
the default for its key. Blank, null and non-string values are ignored.
Extra keys are passed through for consumers that define their own labels.
API
The content type endpoints accept singular_name, plural_name ( each a
nullable string, max 255 ) and labels ( a nullable object of at most 50
entries; each key must match ^[a-z][a-z0-9_]{0,63}$ and each value is a
nullable string, max 255 ). Responses include the stored singular_name,
plural_name and labels, plus resolved_labels: the full label set with
fallbacks and overrides applied.
Escape labels on output. Label values are free-form strings stored by users who can create or update content types. Render them as text, not HTML ( Blade's
{{ }}, React's default text rendering ).
Table Naming Conventions
Follow these conventions when naming your content type tables:
1. Use Snake Case
✓ 'table_name' => 'products'
✓ 'table_name' => 'product_reviews'
✗ 'table_name' => 'Products'
✗ 'table_name' => 'productReviews'
2. Use Plural Forms
✓ 'table_name' => 'products'
✓ 'table_name' => 'events'
✗ 'table_name' => 'product'
✗ 'table_name' => 'event'
3. Taxonomy Tables
Prefix taxonomy tables with the content type:
✓ 'product_categories'
✓ 'product_tags'
✓ 'event_types'
✗ 'categories'
✗ 'tags'
4. Pivot Tables
Use descriptive names for pivot tables:
✓ 'post_category_pivots'
✓ 'product_tag_pivots'
✗ 'category_post'
✗ 'tag_product'
Admin Pages Configuration
Admin pages allow automatic registration of CRUD interfaces in the admin menu.
Configuration Structure
'admin_pages' => [
'listing' => [
'enabled' => true,
'title' => 'Products', // Menu title
'slug' => 'products', // Route slug
'component' => 'Modules\\Shop\\Livewire\\Products\\Index',
'capability' => 'products.view', // Required capability
'icon' => 'fas-shopping-cart', // FontAwesome icon
'position' => 30, // Menu position
'parent_slug' => null, // Top-level menu
],
'create' => [
'enabled' => true,
'title' => 'Add New',
'slug' => 'products.create',
'component' => 'Modules\\Shop\\Livewire\\Products\\Create',
'capability' => 'products.create',
'parent_slug' => 'products', // Child of listing
],
'edit' => [
'enabled' => true,
'title' => 'Edit Product',
'slug' => 'products.edit',
'component' => 'Modules\\Shop\\Livewire\\Products\\Edit',
'capability' => 'products.edit',
'parent_slug' => 'products',
'hidden' => true, // Don't show in menu
],
]
Routes
Routes are automatically registered based on admin pages configuration:
- Listing:
/admin/{slug} - Create:
/admin/{slug}/create - Edit:
/admin/{slug}/{id}/edit
Best Practices
1. Consistent Naming
Use consistent naming across slug, table, and model:
// Good
'slug' => 'products',
'table_name' => 'products',
'model_class' => 'App\\Models\\Product',
// Bad
'slug' => 'product',
'table_name' => 'product_items',
'model_class' => 'App\\Models\\ProductModel',
2. Use Capabilities Format
Follow the feature.capability format:
✓ 'products.view'
✓ 'products.create'
✓ 'products.edit'
✗ 'view_products'
✗ 'can_edit_product'
3. Set Appropriate Defaults
Configure sensible defaults for your content type:
'hierarchical' => false, // Only true if needed
'has_archive' => true, // Most content types need archives
'public' => true, // Unless it's internal only
'show_in_admin' => true, // Unless managing via code only
4. Create Migration First
Always create the database table before registering the content type:
php artisan make:migration create_products_table
5. Use Admin Pages
Configure admin pages to automatically create admin UI:
'admin_pages' => [
'listing' => ['enabled' => true, ...],
'create' => ['enabled' => true, ...],
'edit' => ['enabled' => true, ...],
]
Examples
Simple Content Type (Events)
$contentTypeManager->register([
'name' => 'Events',
'slug' => 'events',
'table_name' => 'events',
'model_class' => 'App\\Models\\Event',
'description' => 'Manage upcoming events',
'supports' => ['title', 'content', 'excerpt', 'featured_image'],
'has_archive' => true,
'archive_slug' => 'events',
'public' => true,
'show_in_admin' => true,
'icon' => 'fas-calendar',
'menu_position' => 25,
]);
Hierarchical Content Type (Documentation)
$contentTypeManager->register([
'name' => 'Documentation',
'slug' => 'docs',
'table_name' => 'docs',
'model_class' => 'App\\Models\\Doc',
'hierarchical' => true, // Enable parent-child
'has_archive' => true,
'archive_slug' => 'documentation',
'supports' => ['title', 'content'],
'public' => true,
'show_in_admin' => true,
'icon' => 'fas-book',
]);
Internal Content Type (Form Submissions)
$contentTypeManager->register([
'name' => 'Form Submissions',
'slug' => 'submissions',
'table_name' => 'form_submissions',
'model_class' => 'App\\Models\\FormSubmission',
'hierarchical' => false,
'has_archive' => false, // No public archives
'public' => false, // Admin only
'show_in_admin' => true,
'icon' => 'fas-envelope',
'supports' => [], // Custom fields only
]);
Retrieving Content Types
Get All Registered Content Types
$contentTypeManager = app(ContentTypeManager::class);
$contentTypes = $contentTypeManager->getRegisteredContentTypes();
Get Specific Content Type
$contentType = $contentTypeManager->getContentType('products');
Check if Content Type Exists
if ($contentTypeManager->contentTypeExists('products')) {
// Content type exists
}
Get Model Instance
$contentType = ContentType::where('slug', 'products')->first();
$modelInstance = $contentType->getModelInstance();
Check Supported Features
if ($contentType->supportsFeature('featured_image')) {
// Content type supports featured images
}
Content Status Enum (v1.1.0)
Content types that support publishing (posts, pages) use the ContentStatus enum instead of plain strings:
use ArtisanPackUI\CMSFramework\Modules\ContentTypes\Enums\ContentStatus;
// Query with enum
$posts = Post::where('status', ContentStatus::Published)->get();
// Use scopes via HasContentStatus trait
$published = Post::published()->get();
$drafts = Post::draft()->get();
// Check status
if ($post->isPublished()) {
// ...
}
Available statuses: Draft, Published, Scheduled, Private.
See developer/enums for the complete enum reference and developer/traits for the HasContentStatus and HasContentFilters traits.

