Plugin anatomy
The file layout every Contensio plugin follows, the namespace convention, and what each file does.
Every Contensio plugin follows the same folder structure. Here's the full layout - most files are optional; only three are strictly required.
The minimum
acme/plugin-awesome/
├── plugin.json <- required
├── composer.json <- required (for Packagist)
├── LICENSE <- required
└── src/
└── AwesomeServiceProvider.php <- required: the entry point
The full layout
acme/plugin-awesome/
├── plugin.json
├── composer.json
├── LICENSE
├── README.md
├── .gitignore
│
├── src/ PHP code
│ ├── AwesomeServiceProvider.php
│ ├── Http/
│ │ └── Controllers/
│ │ ├── Admin/
│ │ │ └── SettingsController.php
│ │ └── Frontend/
│ │ └── AwesomeController.php
│ ├── Models/
│ │ └── AwesomeItem.php
│ ├── Support/
│ │ └── AwesomeConfig.php
│ └── Widgets/
│ └── AwesomeWidget.php
│
├── routes/
│ ├── admin.php admin routes (auth-gated)
│ └── public.php public frontend routes
│
├── database/
│ └── migrations/
│ └── 2026_05_01_000001_create_contensio_awesome_items_table.php
│
├── lang/
│ ├── en/
│ │ └── acme-awesome.php
│ └── fr/
│ └── acme-awesome.php
│
├── resources/
│ └── views/
│ ├── admin/
│ │ ├── index.blade.php
│ │ └── form.blade.php
│ └── partials/
│ └── settings-hub-card.blade.php
│
├── config/
│ └── acme-awesome.php optional developer config
│
└── public/ published assets (optional)
├── css/
│ └── acme-awesome.css
└── js/
└── acme-awesome.js
The namespace convention
Every plugin gets a single namespace string ($ns) that it uses everywhere: view loading, translation loading, config merging, route name prefixes, publish tags, and public asset paths.
The formula is:
{vendor}-{slug} where slug = composer name minus the 'plugin-' prefix
Examples:
| Composer name | $ns |
|---|---|
acme/plugin-awesome |
acme-awesome |
contensio/plugin-social-connect |
contensio-social-connect |
contensio/plugin-ads-manager |
contensio-ads-manager |
This is a hard requirement for the Contensio plugin directory. Two plugins from different vendors can share the same short name (e.g. both called shop) - the vendor prefix in the namespace prevents all collisions in views, config, translations, route names, and Artisan commands.
The $ns property is declared on the service provider:
class AwesomeServiceProvider extends ServiceProvider
{
protected string $ns = 'acme-awesome';
public function boot(): void
{
$this->loadViewsFrom(__DIR__ . '/../resources/views', $this->ns);
// ...
}
}
What each file does
plugin.json - the manifest
The most important file. Contensio's registry reads this to learn the plugin's name, provider class, and how it appears in the admin.
See plugin.json reference for every field.
composer.json - for Packagist
Identical responsibilities to plugin.json but in the Composer world. The extra.cms block tells Contensio this is a plugin:
{
"name": "acme/plugin-awesome",
"type": "library",
"extra": {
"cms": {
"type": "plugin",
"provider": "Acme\\Awesome\\AwesomeServiceProvider"
}
}
}
LICENSE
Required for all plugins. Use AGPL-3.0-or-later for plugins you release on the Contensio directory. Any OSI-approved license is accepted.
src/{Name}ServiceProvider.php - the entry point
A standard Laravel service provider. Contensio boots it when the plugin is enabled. This is where you load routes, views, migrations, register hooks, and declare the $ns.
See The service provider for a full walkthrough.
routes/admin.php and routes/public.php
Route declarations, split by auth context. Both are loaded by your service provider. Admin routes use the contensio.auth + contensio.admin middleware group. Public routes that need to reference runtime config (like a URL prefix stored in settings) should be registered via $this->app->booted() so the config is available.
A simple plugin with only an admin settings page can use a single routes/web.php instead.
database/migrations/
Standard Laravel migrations. Run automatically when the user enables your plugin. Table names must be prefixed with the vendor name:
contensio_awesome_items (for acme/plugin-awesome - if vendor is "contensio")
acme_awesome_items (for acme/plugin-awesome - if vendor is "acme")
See Migrations and Data storage for the full strategy.
lang/{locale}/{ns}.php
Per-locale language files. The file name must match $ns (acme-awesome.php). Loaded under the same namespace as views.
resources/views/
Blade views, loaded under $ns. Admin views extend contensio::admin.layout. Frontend views extend layouts.public.
config/{ns}.php
Developer-level configuration (not admin-editable). Most plugins don't need this - use PluginOptions (stored in DB, admin-editable via a settings page) instead.
public/css/ and public/js/
Static assets. Published to public/vendor/{ns}/ via $this->publishes(). Only needed if your plugin ships its own CSS or JS.
Next
Head to The service provider for a complete walkthrough of the entry point.