For developers
What WordPress developers need to know about themes, plugins, and hooks in Contensio — with side-by-side code examples.
If you've built WordPress themes or plugins, Contensio will feel immediately familiar. The mental models are the same. Most of the differences are improvements.
This page is a practical orientation — for deep dives, the linked articles go further.
Themes
WordPress and Contensio use the same most-specific-first template hierarchy. The file names map 1:1, just with .blade.php instead of .php.
| WordPress | Contensio |
|---|---|
single-post.php |
single-post.blade.php |
page-about.php |
page-about.blade.php |
archive-recipe.php |
archive-recipe.blade.php |
category.php |
category.blade.php |
index.php |
index.blade.php |
Instead of WordPress template tags (the_title(), the_content(), get_header()), Contensio passes data as Blade variables and uses @extends / @section / @yield for layout.
WordPress
<?php get_header(); ?>
<h1><?php the_title(); ?></h1>
<?php the_content(); ?>
<?php get_footer(); ?>
Contensio
@extends('theme::layout')
@section('content')
<h1>{{ $translation->title }}</h1>
{!! apply_filters('contensio/frontend/content-body', $body, $content) !!}
@endsection
Full guide: Creating a theme · Template hierarchy · Themes vs WordPress
Plugins / hooks
Contensio uses the same four functions as WordPress:
add_action('contensio/content/created', $callback);
do_action('contensio/content/created', $content);
add_filter('contensio/frontend/content-body', $callback);
apply_filters('contensio/frontend/content-body', $html, $content);
The differences:
- Hook names use
/not_—contensio/content/createdinstead ofsave_post - Full Eloquent models — you receive
Content $content, not a bare$post_id - No
$accepted_args— PHP handles variadic args; just declare what you need - Registered from a service provider — no
functions.phpfile scan
WordPress
add_action('save_post', function ($post_id) {
$post = get_post($post_id); // extra query
notify($post->post_title);
}, 10, 1);
Contensio
add_action('contensio/content/created', function (Content $content) {
notify($content->translations->first()?->title); // already loaded
});
Full guide: Hook system · Hooks vs WordPress
WordPress hook → Contensio hook
| WordPress | Contensio |
|---|---|
save_post (new) |
contensio/content/created |
post_updated |
contensio/content/updated |
transition_post_status |
contensio/content/status-changed |
before_delete_post |
contensio/content/deleting |
deleted_post |
contensio/content/deleted |
user_register |
contensio/user/registered |
wp_login |
contensio/user/login |
wp_logout |
contensio/user/logout |
add_attachment |
contensio/media/uploaded |
comment_post |
contensio/comment/submitted |
wp_head |
contensio/frontend/head |
wp_body_open |
contensio/frontend/body-open |
wp_footer |
contensio/frontend/footer |
the_content filter |
contensio/frontend/content-body |
wp_title filter |
contensio/frontend/page-title |
Plugin structure
A WordPress plugin is a PHP file with a header comment, discovered by file scan.
A Contensio plugin is a Composer package (or a local package in packages/plugins/) with a Laravel service provider. You get autoloading, dependency injection, testability, and migrations for free.
packages/plugins/acme/my-plugin/
plugin.json
src/
MyPluginServiceProvider.php ← hooks registered here in boot()
Http/Controllers/
Models/
resources/views/
database/migrations/
class MyPluginServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Hooks
add_action('contensio/content/created', [$this, 'onContentCreated']);
add_filter('contensio/frontend/content-body', [$this, 'filterBody']);
// Routes
$this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
// Views
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'myplugin');
// Migrations
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
}
}
Full guide: Plugin anatomy · Service provider
functions.php equivalent
WordPress themes load functions.php for setup code. In Contensio, you don't need it for most things:
functions.php task |
Contensio equivalent |
|---|---|
| Register nav menus | Configure menus in Appearance → Menus |
| Add theme support | Not needed — features are always available |
| Enqueue scripts | Direct <link> / <script> in layout.blade.php |
| Register widget areas | Use Hook::render() slots in your theme |
| Add hooks | In a plugin's service provider boot() |
| Define helper functions | Helpers in a plugin or app/helpers.php |
If your theme needs PHP logic, ship a service provider as part of your theme package.
ACF / custom fields
Advanced Custom Fields is a plugin for custom meta on posts in WordPress. Contensio has custom fields built in.
| ACF concept | Contensio equivalent |
|---|---|
| Field group | Field group (assigned to a content type) |
| Text, Textarea, Number, Email, URL | Same field types |
| Image | Media picker field |
| True/False | Checkbox field |
| Select, Multi-select | Select, Multi-select |
| Repeater | Not yet available |
| Relationship | Not yet available |
| Page builder (Flexible Content) | Block editor |
Fields are available in templates via $fieldValues (keyed by {fieldId}:{langId} or {fieldId}:_ for non-translatable fields).
WP_Query equivalent
WordPress uses WP_Query to fetch posts with filters. In Contensio you use Eloquent directly.
WordPress
$query = new WP_Query([
'post_type' => 'recipe',
'posts_per_page' => 12,
'tax_query' => [
['taxonomy' => 'cuisine', 'field' => 'slug', 'terms' => 'italian'],
],
]);
while ($query->have_posts()) { $query->the_post(); }
Contensio
use Contensio\Models\Content;
$posts = Content::published()
->ofType('recipe')
->withTerm('cuisine', 'italian')
->with(['translations', 'featuredImage'])
->paginate(12);
See Querying content for the full scope/method reference.
What you won't miss
A few WordPress pain points that simply don't exist in Contensio:
- No
$accepted_argscounting — declare the parameters you need, PHP handles the rest - No plugin activation hooks (
register_activation_hook) — use Laravel migrations instead - No global
$post/$wp_query— data is passed directly to the view, no global state - No white screen of death — Laravel's error handling catches and reports exceptions without crashing the page
- No update nags — standard Composer version management