Extension Model
Choose an App contribution, publish it through the manifest, and understand how Core makes it available.
Extension Model
Choose the capability
To add a platform feature, implement its SDK contribution interface in your App's declared contribution directory. Core discovers it during manifest building and passes its declarations to the owning runtime.
| Your task | Implementation guide |
|---|---|
| Publish a Resource and its permissions | Create a Resource |
| Add an App menu destination | Add a menu destination |
| Add settings | Add a settings page |
| Add a dashboard or slot widget | Add a Dashboard Widget, Add a Slot Widget |
| Let another App reference records | Resource References |
| Connect a process, approval, or signature | Business Process, Electronic Approval, Electronic Signature |
For exact interfaces, return types, and descriptor parameters, use Contribution contracts. You do not need to learn every contribution family before implementing one.
Follow the declaration
Manifest locations → classes implementing SDK interfaces → generated contribution index → runtime catalog → tenant and actor checks → App behavior.
The generated src/WorkshopAppManifest.php supplies namespace/directory pairs. In this method excerpt, paths are relative to that class,
so the package does not depend on a Core checkout layout.
public function contributionLocations(): array
{
return [
['namespace' => 'Amuzcorp\Nexia\Workshop\Contribution', 'directory' => __DIR__.'/Contribution'],
['namespace' => 'Amuzcorp\Nexia\Workshop\Descriptors', 'directory' => __DIR__.'/Descriptors'],
];
}A class must explicitly implement the interface. A trait that supplies its methods does not make the class discoverable. Subdirectories are included. Declaration methods return static metadata; they must not query tenant rows, read the current actor, or perform writes. Runtime providers receive the context their contract declares when Core invokes them.
After changing contribution classes, rebuild the runtime manifests:
task artisan -- nexia-runtime:refresh-runtime-caches
task artisan -- nexia-apps:doctor-package-app workshopThe doctor should discover the expected interfaces without failures. These checks establish wiring, not user access. A long-running process may also need a restart; see Fix contribution discovery.
Keep related declarations together
A generated Resource Module holds the facets for one Resource: stable key, model binding, authorization, permissions, navigation, and public descriptor. It does not become a package-wide registry. Non-Resource capabilities can live in dedicated classes, grouped by the runtime they serve.
A contributor is the discoverable class. A descriptor is a typed value
it publishes. AppDescriptorContribution::appDescriptors() returns an
AppDescriptorSet; host consumers select the descriptor families they support.
Some provider-bearing interfaces also supply App behavior for runtime calls.
The Resource's catalog entry describes the Resource type, not its tenant rows. An App's editable master data belongs in its model and API. Catalog keys appear in permissions, translations, and configuration; choose them before publishing and do not rename them as a cosmetic change.
Choose who owns the result
| Kind | Result | What a later release changes |
|---|---|---|
| Catalog | Code-owned capability definitions | Definitions after synchronization/cache refresh |
| Descriptor | Typed runtime metadata with stable identity | Supported future resolution; existing references need compatibility |
| Role preset | An administrator's starting permission selection | Future selections, not existing grants |
| Copyable template | Tenant-owned content created on explicit application | Future copies, not existing tenant edits |
| Required installation data | Minimum tenant rows needed to operate | Only through an explicit safe migration or supported repair path |
A Process Template descriptor supplies an editor starting point; saving and publishing produces the tenant's definition. It is distinct from applying a copyable template that writes rows and records the application.
Use Role Presets, Copyable templates, and Required installation content for their separate procedures. None of these mechanisms grants authority by itself.
Descriptor lifecycle and versions
A descriptor's version is separate from the App package release. The owning runtime decides which identity and version it persists. Do not assume every family pins versions in the same way.
| Status | New selection | Existing work |
|---|---|---|
Active | Eligible, subject to runtime gates | May resolve |
Deprecated | Excluded from new authoring | May still resolve where the runtime supports existing references |
Removed | Unavailable | Resolution is refused |
Retire a capability by deprecating it, draining or explicitly migrating its references, then removing it. Deleting its source early can break running work. Process external tasks, published definitions, approval bindings, and template copies have different persistence rules; follow the specific Business Process contracts or Electronic Approval contracts.
A Resource lifecycle event is a facet of its parent Resource descriptor and has
an integer payload schemaVersion. A standalone public integration event has
its own event declaration. Neither version substitutes for the Composer package
tag. See Release an App.
Discovery and access are separate
| Check | Question |
|---|---|
| Discovery | Is the class indexed under the required interface? |
| App availability | Is its owner operational for this tenant? |
| Permission | Does this capability require a permission, and does the actor hold it? |
| Lifecycle and compatibility | Can this runtime use this descriptor for this operation? |
Permission and lifecycle behavior varies by surface; a nullable navigation permission and an existing process task are different cases. The final App handler must still validate inputs, authorize the record, and enforce business state. If the feature is missing, diagnose these layers in Fix contribution discovery.