Turn on debug mode
.env
DEBUG=true:
- Exceptions thrown while loading or booting a plugin are rethrown instead of hidden.
- Plugins previously recorded as failed are booted anyway, so you can see the error again.
- Twig and route caches are off, so template and route changes apply immediately.
- Twig runs with strict variables, and undefined variables raise errors instead of rendering nothing.
Plugin health
When debug mode is off and a plugin causes a fatal error during boot, the failure is written tovar/plugin-health.json, and the admin panel shows the plugin as failed. It stays skipped until someone reactivates it, which clears the record.
Only fatal errors, such as a parse error or a missing class, are recorded this way. A catchable exception in
boot() is skipped silently in production, which is why development should always run with DEBUG=true.Logs
An unhandled exception in production returns
{"message": "Internal error", "id": "..."}. Search that ID in the error log to find the stack trace.
Common problems
The plugin isn't listed in the admin panel
The plugin isn't listed in the admin panel
The manifest failed validation, or the package isn’t where Aikeedo looks.
- The directory must be
extra/extensions/{vendor}/{name}and match thenamefield. typemust beaikeedo-plugin.requiremust includeheyaikeedo/composer.extra.entry-classmust be set.
InvalidPluginComposerJsonFileException, which debug mode surfaces.Class ... not found
Class ... not found
Composer doesn’t know about your package.Check that
autoload.psr-4 maps your namespace to src/, and that the namespace in extra.entry-class matches the class exactly, including escaped backslashes in JSON.PluginInterfaceNotImplementedException
PluginInterfaceNotImplementedException
The entry class exists but doesn’t implement
Plugin\Domain\PluginInterface. Implement it and define boot(Context $context): void.The plugin is active, but boot() never runs
The plugin is active, but boot() never runs
boot() runs on the request after activation. Reload the page. If it still doesn’t run, the plugin may be marked as failed: check var/plugin-health.json and the admin panel.A route returns 404
A route returns 404
- Did you call
AttributeMapper::addPath()for the directory that holds the handler? - Does the handler implement
Psr\Http\Server\RequestHandlerInterface? - Is the route cached? Set
CACHE=false, or clear the cache from Status → Clear cache. - Is the full path what you expect? The base class adds a prefix such as
/adminor/api.
Template not found
Template not found
The namespace passed to
FilesystemLoader::addPath($dir, 'acme-hello') must match the @acme-hello/... reference, the path must exist, and the plugin must be active so boot() ran.An injected option is always null
An injected option is always null
The option hasn’t been saved yet, or the dot path is wrong.
#[Inject('option.hello.api_key')] matches the JSON under the option key hello. Confirm what was stored by dumping option.hello in a template.Settings don't save
Settings don't save
The form needs
x-ref="form", @submit.prevent="submit" and {% set xdata = 'settings' %}. Check the browser network tab for the POST /admin/api/options request.Changes to a public asset don't appear
Changes to a public asset don't appear
Files listed in
extra.public are copied at install time. Re-run composer require acme/hello, or copy the file into public/e/{vendor}/{name}/ while iterating.The whole site is blank after installing a plugin
The whole site is blank after installing a plugin
A fatal error during boot, with debug mode off and a stale health record missing. Check
var/log/error-*.log, then remove the plugin directory or run composer remove acme/hello to recover.Useful checks
bin/console too, with a visible stack trace.
Before you release
The plugin installs cleanly into an installation that never had it.
Activating, deactivating and uninstalling all work, and uninstalling leaves nothing behind.
No errors appear in
var/log with DEBUG=true.Static analysis passes:
vendor/bin/phpstan analyse --level=5 extra/extensions/acme/hello/src.The plugin behaves when its settings are empty, since that’s the state right after install.