Discovery is the process of scanning module sources, reading metadata, and producing the manifest and registrar structures the registry consumes. How this happens depends on the panel's mode, abstracted behind a ModuleSource interface.
ModuleSource
ModuleSource is the interface that the registry consumes. It provides the full set of module manifests and registrar metadata regardless of how they were produced. Two implementations:
CachedModuleSource (production)
Used in production mode. Loads bundled module metadata from a prebuilt file shipped inside the binary, and external module metadata from a JSON cache file at $paths->cache. Both are hydrated via fromArray(). No reflection occurs at runtime.
LiveModuleSource (debug)
Used in debug mode. Discovers and reflects all modules at boot:
- Bundled modules are reflected from their registrar classes in the panel's own codebase
- External modules are discovered from
$paths->modules using Composer as a library, then their registrar classes are reflected
Changes to module code are picked up immediately without rebuilding or re-running discovery. This is the expected mode for local development and module development in Docker.
Bundled modules
First-party modules shipped inside the binary as part of the panel's own codebase. Their manifests are declared in code. In production mode their registrar metadata is prebuilt during the build step. In debug mode their registrar classes are reflected at boot.
External module discovery
External modules are installed into the modules directory ($paths->modules), which is a self-contained Composer project. Discovery reads the Composer lock file inside this directory to find all packages of type tgp-module. Composer is used as a library rather than a standalone tool.
The provide block in the modules composer.json is regenerated from the panel's own lock file during discovery to prevent dependency conflicts.
Module metadata
Each external module declares metadata in its composer.json under the extra.tgp key:
name — human-readable display name
registrar — fully qualified class name of the module's registrar
capabilities — list of capability identifiers the module declares
icon — optional icon reference
definition — optional module definition metadata
Standard Composer fields (package name, version, description, autoload) are read from the package itself.
ModuleManifest
ModuleManifest is a final readonly DTO containing the full resolved metadata for a single module:
- Module ident (derived from package name for external, declared for bundled)
- Vendor, version, name, description
- Capabilities (as
Capability enum cases)
- Core flag (true for bundled modules)
- Registrar class name
- Icon, definition
Built by ModuleManifestBuilder from a Composer package for external modules. Bundled modules construct their manifests directly.
ModuleRegistrar (metadata)
ModuleRegistrar is a final readonly metadata class containing reflected metadata about a registrar class:
- Which methods have
#[Register], #[Boot], and #[Collect] attributes
- For
#[Collect] methods: the collector type accepted, whether #[Unscoped] is present, and the optional PanelContext parameter
Built by ModuleRegistrarBuilder using reflection. In production this happens at build time (bundled) or during discovery (external). In debug mode this happens at boot for all modules.
Capability enum
Capability is a string-backed enum declaring all recognised capabilities (CrossRoutes, ExtendSchema, ModifyUi, DaemonAccess, etc.). Capabilities are declared by modules in their manifest and checked at runtime by the code that enforces them. The module system itself does not enforce capabilities.
Registrar attributes
#[Register] — marks the method called during the register lifecycle phase
#[Boot] — marks the method called during the boot lifecycle phase
#[Collect] — marks a method that participates in collection for a given collector type
#[Unscoped] — marks a #[Collect] method as operating outside its module's implicit scope
Cache file
Discovery produces a JSON cache file written to $paths->cache. The file contains external module data only:
- A map of module idents to serialised
ModuleManifest data
- A map of module idents to serialised
ModuleRegistrar metadata
The file is regenerated in full on every discovery run. At boot, CachedModuleSource hydrates external manifests and registrars from this file via fromArray().
Prebuilt registrar file
During the build step, bundled module registrar classes are reflected and the resulting metadata is written to a generated file that ships inside the binary. CachedModuleSource loads this at boot in production mode. LiveModuleSource ignores it and reflects at boot instead.
DI integration
#[Manifest('ident')] resolvable attribute and resolver — injects a ModuleManifest for the given ident
#[Registrar('ident')] resolvable attribute and resolver — injects a ModuleRegistrar for the given ident
Tasks
Discovery is the process of scanning module sources, reading metadata, and producing the manifest and registrar structures the registry consumes. How this happens depends on the panel's mode, abstracted behind a
ModuleSourceinterface.ModuleSource
ModuleSourceis the interface that the registry consumes. It provides the full set of module manifests and registrar metadata regardless of how they were produced. Two implementations:CachedModuleSource (production)
Used in production mode. Loads bundled module metadata from a prebuilt file shipped inside the binary, and external module metadata from a JSON cache file at
$paths->cache. Both are hydrated viafromArray(). No reflection occurs at runtime.LiveModuleSource (debug)
Used in debug mode. Discovers and reflects all modules at boot:
$paths->modulesusing Composer as a library, then their registrar classes are reflectedChanges to module code are picked up immediately without rebuilding or re-running discovery. This is the expected mode for local development and module development in Docker.
Bundled modules
First-party modules shipped inside the binary as part of the panel's own codebase. Their manifests are declared in code. In production mode their registrar metadata is prebuilt during the build step. In debug mode their registrar classes are reflected at boot.
External module discovery
External modules are installed into the modules directory (
$paths->modules), which is a self-contained Composer project. Discovery reads the Composer lock file inside this directory to find all packages of typetgp-module. Composer is used as a library rather than a standalone tool.The
provideblock in the modulescomposer.jsonis regenerated from the panel's own lock file during discovery to prevent dependency conflicts.Module metadata
Each external module declares metadata in its
composer.jsonunder theextra.tgpkey:name— human-readable display nameregistrar— fully qualified class name of the module's registrarcapabilities— list of capability identifiers the module declaresicon— optional icon referencedefinition— optional module definition metadataStandard Composer fields (package name, version, description, autoload) are read from the package itself.
ModuleManifest
ModuleManifestis a final readonly DTO containing the full resolved metadata for a single module:Capabilityenum cases)Built by
ModuleManifestBuilderfrom a Composer package for external modules. Bundled modules construct their manifests directly.ModuleRegistrar (metadata)
ModuleRegistraris a final readonly metadata class containing reflected metadata about a registrar class:#[Register],#[Boot], and#[Collect]attributes#[Collect]methods: the collector type accepted, whether#[Unscoped]is present, and the optionalPanelContextparameterBuilt by
ModuleRegistrarBuilderusing reflection. In production this happens at build time (bundled) or during discovery (external). In debug mode this happens at boot for all modules.Capability enum
Capabilityis a string-backed enum declaring all recognised capabilities (CrossRoutes,ExtendSchema,ModifyUi,DaemonAccess, etc.). Capabilities are declared by modules in their manifest and checked at runtime by the code that enforces them. The module system itself does not enforce capabilities.Registrar attributes
#[Register]— marks the method called during the register lifecycle phase#[Boot]— marks the method called during the boot lifecycle phase#[Collect]— marks a method that participates in collection for a given collector type#[Unscoped]— marks a#[Collect]method as operating outside its module's implicit scopeCache file
Discovery produces a JSON cache file written to
$paths->cache. The file contains external module data only:ModuleManifestdataModuleRegistrarmetadataThe file is regenerated in full on every discovery run. At boot,
CachedModuleSourcehydrates external manifests and registrars from this file viafromArray().Prebuilt registrar file
During the build step, bundled module registrar classes are reflected and the resulting metadata is written to a generated file that ships inside the binary.
CachedModuleSourceloads this at boot in production mode.LiveModuleSourceignores it and reflects at boot instead.DI integration
#[Manifest('ident')]resolvable attribute and resolver — injects aModuleManifestfor the given ident#[Registrar('ident')]resolvable attribute and resolver — injects aModuleRegistrarfor the given identTasks
Capabilityenum with all capability cases#[Register],#[Boot],#[Collect], and#[Unscoped]registrar attributesModuleManifestfinal readonly DTO withfromArray()ModuleManifestBuilderfor constructing a manifest from a Composer packageModuleRegistrarfinal readonly metadata class withfromArray()ModuleRegistrarBuilderusing reflection on the registrar classModuleSourceinterfaceCachedModuleSourceloading from prebuilt file and JSON cacheLiveModuleSourcewith full discovery and reflection at bootprovideblock regeneration from the panel lock file#[Manifest]resolvable attribute and resolver#[Registrar]resolvable attribute and resolverModuleManifestBuilderfrom a mock Composer packageModuleRegistrarBuilderreflection outputCachedModuleSourceloading and hydrationLiveModuleSourcediscovery and reflection#[Manifest]and#[Registrar]resolution