Troubleshooting
Common plugin development issues and how to fix them.
Plugin doesn't appear in the admin Plugins list
Check the manifest location.
- Composer install:
vendor/{vendor}/{package}/plugin.jsonmust exist. - Local install:
packages/plugins/{vendor}/{package}/plugin.json.
Check the manifest is valid JSON.
php -r "echo json_decode(file_get_contents('plugin.json'))->name ?? 'INVALID';"
If it prints "INVALID", your JSON is malformed.
Check extra.cms.type in composer.json (only matters for Composer-installed plugins):
"extra": {
"cms": { "type": "plugin" }
}
Without this, PluginRegistry skips the package.
"Class not found" after enabling
Your autoload PSR-4 mapping is wrong or the autoloader wasn't regenerated.
For Composer-installed plugins:
composer dump-autoload
For local/ZIP plugins: Contensio registers the PSR-4 mapping from plugin.json on boot. Make sure the mapping matches your actual folder:
"autoload": {
"psr-4": {
"Acme\\Awesome\\": "src/"
}
}
File at src/AwesomeServiceProvider.php must declare namespace Acme\Awesome;.
Enable succeeds but nothing happens
- Service provider's
boot()isn't being called, or you put all your logic inregister(). - Move everything to
boot()except service container bindings.
// register() — only service container stuff
public function register(): void
{
$this->app->singleton(MyService::class);
}
// boot() — everything else
public function boot(): void
{
Hook::add('login.after_form', fn () => view('awesome::button')->render());
// ...
}
Migrations didn't run on enable
Check:
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations')is inboot().- The migration files follow the Laravel naming convention:
YYYY_MM_DD_NNNNNN_description.php. - Each migration returns
new class extends Migration { ... }(Laravel 9+ style).
If a migration fails silently, tail storage/logs/laravel.log — Contensio catches migration exceptions during enable and logs them without failing enable itself.
Route returns 404
Check the route is registered:
php artisan route:list | grep awesome
If your route isn't there:
$this->loadRoutesFrom(__DIR__ . '/../routes/web.php')inboot()?- Plugin is enabled?
- Laravel route cache is stale? Run
php artisan route:clear.
If the route is there but returns 404:
- Middleware misconfiguration (e.g.
cms.adminrequired but user is logged in as a non-admin). - URL typo.
View returns "View [...] not found"
You're calling a view without its namespace, or the namespace wasn't registered.
// Wrong
return view('admin.settings'); // looks in main app views, not plugin
// Right
return view('awesome::admin.settings');
Namespace must match the one in loadViewsFrom():
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'awesome');
// ^ this is the namespace
"Target class does not exist" when a hook fires
Your hook callback references a class that doesn't autoload:
Hook::add('login.after_form', fn () => SomeClass::render());
// SomeClass is in src/Support/SomeClass.php but namespace is wrong
Check the namespace in SomeClass.php matches your PSR-4 mapping.
Blade syntax errors in plugin views
Contensio compiles all views into cached PHP. If you edit a Blade file while the admin is open, you might get a stale cached version. Fix:
php artisan view:clear
If the error persists, it's a real Blade syntax issue — check for unmatched @endif, @endforeach, {{ }} blocks.
Hook fires but no output appears
Your callback probably returned null or void. Hooks must return a string:
Hook::add('login.after_form', function () {
return view('awesome::button')->render(); // ← needs ->render() to return a string
});
view(...) returns a View object. Call ->render() to get the string.
Plugin conflicts with another plugin
- Route name collision — both plugins declared the same name. Rename yours with a unique prefix.
- Table name collision — same fix: prefix with your plugin slug.
- Hook collision — two plugins registering for the same hook is fine; both run. Use priorities if order matters.
"Permission does not exist" when viewing a sidebar entry
Your plugin.json menu.permission references a permission that wasn't seeded. Either:
- Reference a core permission that definitely exists (
plugins.configure) - Register your own permission in a migration (see Permissions)
Plugin works locally, breaks on user's install
Likely causes:
- Case sensitivity — Windows (dev) is case-insensitive, Linux (prod) isn't. A file
src/SomeClass.phpused asuse Acme\someclass;works locally and breaks in prod. Always match case exactly. - Autoload dump — user forgot
composer dump-autoloadafter install. Contensio runs this automatically on ZIP install; Composer installs handle it themselves.
Asking for help
If this page didn't solve your problem, open an issue at github.com/contensio/contensio/issues with:
- The error message + stack trace (from
storage/logs/laravel.log) - Your
plugin.json - Your service provider's
boot()method - Contensio + PHP + Laravel versions