Contensio logo

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.json must 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 in register().
  • 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:

  1. $this->loadMigrationsFrom(__DIR__ . '/../database/migrations') is in boot().
  2. The migration files follow the Laravel naming convention: YYYY_MM_DD_NNNNNN_description.php.
  3. 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') in boot()?
  • 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.admin required 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.php used as use Acme\someclass; works locally and breaks in prod. Always match case exactly.
  • Autoload dump — user forgot composer dump-autoload after 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