App manifest
The manifest metadata and hooks used to discover an App and its contributions.
App Manifest
Register the App metadata, contribution locations, and custom routes through this contract. Start from the manifest generated by Create an App Package; the metadata key and manifest class must agree. The host can then discover your App and its declared extensions.
Signature
An App Package manifest extends the SDK base class and satisfies
Nexia\AppRuntime\Contracts\TenantInstallableAppManifest:
AbstractPackageAppManifest supplies three methods as final — you cannot
override them, because they derive from package metadata:
final public function definition(): AppDefinition;
final public function id(): string;
final public function name(): string;
The remaining hooks are yours to override:
public function contributionLocations(): array;
public function pageElements(): array;
public function pageElementsExtras(): array;
public function agentComponents(): array;
public function tenantFilamentResources(): array;
public function tenantMigrationPaths(): array;
The class name must end in AppManifest, and extra.nexia.app.manifest must
name it fully qualified.
Minimal example
After Create an App Package, this is a complete minimal src/WorkshopAppManifest.php. Keep the generated manifest if it also declares your Overview or navigation.
A working manifest declares where its contributions live and where its migrations are:
<?php
declare(strict_types=1);
namespace Amuzcorp\Nexia\Workshop;
use Nexia\AppRuntime\AbstractPackageAppManifest;
final class WorkshopAppManifest extends AbstractPackageAppManifest
{
public function contributionLocations(): array
{
return [
['namespace' => 'Amuzcorp\Nexia\Workshop\Contribution', 'directory' => __DIR__.'/Contribution'],
['namespace' => 'Amuzcorp\Nexia\Workshop\Descriptors', 'directory' => __DIR__.'/Descriptors'],
];
}
public function tenantMigrationPaths(): array
{
return [__DIR__.'/../database/migrations/tenant'];
}
}Resource modules and other contributor classes in these directories publish permissions, navigation, dashboards, and descriptors. The manifest may also implement an App-level contribution interface.
Parameters: The metadata block
definition() is built from extra.nexia.app in your package's
composer.json, not from PHP:
{
"manifest": "Amuzcorp\\Nexia\\Workshop\\WorkshopAppManifest",
"app_family": "other",
"app_icon": "box",
"launcher_order": 500,
"overview_navigation_id": "workshop",
"app_name": "Workshop",
"app_key": "workshop",
"app_table_prefix": "wsp",
"prerequisite_apps": [],
"descriptions": {"en": "Workshop application."},
"readiness": "available"
}descriptions defaults to [] and readiness to available; every other field
is required, and unknown fields fail validation. Full field table:
App Identity.
AppDefinition validates on construction:
| Condition | Message |
|---|---|
manifest is not a FQCN | App metadata [manifest] must be a fully qualified PHP class name. |
app_key is reserved | App metadata [app_key] cannot use reserved Core key [{key}]. |
launcher_order negative | App metadata [launcher_order] must be zero or greater. |
app_family not kebab-case | Kebab-case assertion failure on app_family |
Blank app_name or app_icon | Non-blank assertion failure on that field |
Reserved keys: core, host, platform, shell, tenant, process, apps.
Readiness states are available, beta, and preview. available and
beta are tenant-installable when the remaining catalog and prerequisite gates
pass. preview is visible catalog metadata only: it is never an install target.
Publication is a separate Central decision (draft, published, or
suspended), so manifest metadata alone does not make an App selectable.
Output or return: Hooks
| Method | Returns | Purpose |
|---|---|---|
contributionLocations() | list of {namespace, directory} | Roots the contribution-manifest builder scans and indexes |
pageElements() | array<string, string> | App-level route → component name; the base delegates to pageElementsExtras() |
pageElementsExtras() | array<string, string> | Route → component name for non-Resource pages |
agentComponents() | array | Components an agent renderer may use |
tenantFilamentResources() | list of class-string | Filament tenant Resources to register |
tenantMigrationPaths() | list of path | Directories of tenant migrations |
contributionLocations()
The namespace must match the PSR-4 namespace of classes in directory
exactly. Subdirectories are covered — Contribution/Resources/NoteModule.php
is found by the Contribution entry.
A class outside every declared root is never indexed, whatever interfaces it
implements. Ordinary requests trust the generated manifest and do not rescan
these roots. After a contribution source change, run
nexia-runtime:refresh-runtime-caches.
pageElements() and pageElementsExtras()
The base pageElements() returns pageElementsExtras(). Add App-level routes there. Generated Resource routes come from each module’s ShellResourceContribution, not from a generator-maintained pageElements() method:
Excerpt inside the generated Manifest:
public function pageElementsExtras(): array
{
return [
'/apps/workshop' => 'WorkshopOverviewSurface',
];
}Every published name needs a frontend registration. The doctor's
shell component pairing check fails when one does not, and the route renders
blank. Its frontend component registration check is coarser — it only
confirms that index.ts calls a registration function at all.
tenantMigrationPaths()
Return absolute directories built with __DIR__. The tenant installer runs these migrations for the selected App and its prerequisites before initialization. Later tenants:migrate calls update only Apps with installation history, including disabled Apps. Uninstalled Apps do not contribute schema to a new tenant. Use __DIR__ so both local Composer symlinks and packaged vendor installations resolve correctly.
Optional contribution interfaces
The manifest may itself implement contribution interfaces when the contribution
belongs to the App rather than to one Resource. In practice this is
NavigationContribution, for destinations no Resource owns:
Retain the generated manifest’s NavigationContribution interface, NavigationItem import, and navigationItems() method. Add App-level destinations to that method; the generated Overview already demonstrates the complete call.
Do not move per-Resource permissions, navigation, or descriptors onto the manifest. They belong on the Resource's own catalog contributor, which discovery finds through the declared locations.
Errors
| Message | Cause | Resolution |
|---|---|---|
App manifest [{class}] must end with AppManifest. | Class name convention violated | Rename the class and update extra.nexia.app.manifest |
WARN runtime app registration: AppRegistry cannot see {key} | Package not in host Composer requirements, or stale autoloader | nexia-apps:activate-package-app, then restart processes |
FAIL runtime app metadata: registered manifest metadata differs from composer.json. | Loaded definition diverged from the file | Re-run nexia-access:sync-manifest and activation |
FAIL runtime app metadata: registered manifest is not tenant-installable. | Manifest does not satisfy TenantInstallableAppManifest | Extend AbstractPackageAppManifest |
WARN contribution locations: no package contribution location is registered | Directory not declared, or namespace mismatch | Fix contributionLocations() |
Use in your App
The current Quality manifest declares two contribution locations, one tenant migration path, an overview route, and one navigation item. Its tenant Filament Resource list is empty. Metadata comes from composer.json; use the generated manifest as the starting point for your own App.
Related
- App Identity — every metadata field explained
- Extension Model — what discovery does with the declared locations
- React components and hooks — registering the components
pageElements()names - Fix contribution discovery — diagnosing each failure above