Skip to content
Reference

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:

Code example
PHP
<?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:

Code example
JSON
{
  "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:

ConditionMessage
manifest is not a FQCNApp metadata [manifest] must be a fully qualified PHP class name.
app_key is reservedApp metadata [app_key] cannot use reserved Core key [{key}].
launcher_order negativeApp metadata [launcher_order] must be zero or greater.
app_family not kebab-caseKebab-case assertion failure on app_family
Blank app_name or app_iconNon-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

MethodReturnsPurpose
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()arrayComponents an agent renderer may use
tenantFilamentResources()list of class-stringFilament tenant Resources to register
tenantMigrationPaths()list of pathDirectories 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:

Code example
PHP
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

MessageCauseResolution
App manifest [{class}] must end with AppManifest.Class name convention violatedRename the class and update extra.nexia.app.manifest
WARN runtime app registration: AppRegistry cannot see {key}Package not in host Composer requirements, or stale autoloadernexia-apps:activate-package-app, then restart processes
FAIL runtime app metadata: registered manifest metadata differs from composer.json.Loaded definition diverged from the fileRe-run nexia-access:sync-manifest and activation
FAIL runtime app metadata: registered manifest is not tenant-installable.Manifest does not satisfy TenantInstallableAppManifestExtend AbstractPackageAppManifest
WARN contribution locations: no package contribution location is registeredDirectory not declared, or namespace mismatchFix 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.

Source of truth: docs/developers/content/en/app-sdk/app-manifest.md