Skip to main content
The mechanisms Aikeedo uses to stay extensible are available to your plugin too. Publish an interface, let implementations register themselves under a key, and resolve them at runtime.

Publish an interface

Define the contract in your package, and document it:
src/Domain/NotificationChannelInterface.php
Give the interface a getLookupKey() and an isEnabled(), the same shape the core’s extension points use. It keeps registration, configuration and admin listings predictable.

Register implementations

Shared\Infrastructure\Collections\ServiceCollectionInterface is a keyed registry that works with any interface, not just the core’s:
src/Plugin.php
Services are resolved from the container when they’re first used, so registering is cheap even if the implementation is expensive to build.

Consume implementations

get() throws Shared\Infrastructure\Collections\ServiceNotFoundException when the key isn’t registered, so fall back to a default rather than letting a stale setting break the page.

Let others configure their implementation

Copy the convention the core uses for its own extension points: the registration key doubles as the settings URL. If your plugin lists channels at /admin/settings/alerts, link each one to /admin/settings/alerts/{key}, and let the implementing plugin declare that route and point its extra.default_url there. See Admin settings pages.

Load order

Plugins boot in the order they’re discovered, so don’t assume another plugin has registered before you. Resolve implementations when you use them, not in boot(), and treat an empty collection as normal.

Custom AI capabilities

The AI service factory works the same way. Define a capability interface that extends the base AI contract, then register services for it:
src/Domain/SummaryServiceInterface.php
Implementations still provide supportsModel() and getSupportedModels() from Ai\Domain\Services\AiServiceInterface, which is how the factory picks one.

Document the contract

An extension point is an API. Ship it like one:
  • Publish the interface’s signatures and semantics in your README.
  • Version the contract, and follow semantic versioning when it changes.
  • Say which key namespace implementers should use, such as acme-alerts.*.
  • Provide one reference implementation inside your own package.