Skip to main content
Aikeedo protects a production site from a broken plugin: boot errors are swallowed, fatals are recorded, and the plugin is skipped on later requests. That’s the opposite of what you want while developing, so start by turning debug mode on.

Turn on debug mode

.env
With 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 to var/plugin-health.json, and the admin panel shows the plugin as failed. It stays skipped until someone reactivates it, which clears the record.
The entry names the package, the error, the file and line, and the application version at the time. Reactivating the plugin from Plugins clears it; so does deleting the file.
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 manifest failed validation, or the package isn’t where Aikeedo looks.
  • The directory must be extra/extensions/{vendor}/{name} and match the name field.
  • type must be aikeedo-plugin.
  • require must include heyaikeedo/composer.
  • extra.entry-class must be set.
An invalid manifest raises InvalidPluginComposerJsonFileException, which debug mode surfaces.
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.
The entry class exists but doesn’t implement Plugin\Domain\PluginInterface. Implement it and define boot(Context $context): void.
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.
  • 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 /admin or /api.
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.
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.
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.
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.
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

Booting the console exercises the same bootstrap path as a web request, so a plugin that breaks boot usually breaks 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.