Modules and Application Structure
A module groups a business feature with its pages, API routes, data and translations. Your application chooses which modules to load. Generators collect their declarations so the application can find them.
On this page
Where does your code belong?
In a standalone application, keep a custom feature in its own directory under src/modules. Enable it in src/modules.ts. Framework development uses the product repository, where Core modules live under packages/core/src/modules.
src/modules/service/
src/modules/visit_planning/ # example custom module
src/modules.ts # enabled modulesCode and documentation
From source files to a running application
- Select modules and declare their dependencies.
- Run the application’s generation scripts. Missing declared dependencies stop module registry generation.
- Build the application and check that the expected page, route or service is registered.
Code and documentation
Example: a service module
A service feature can own its case records, API and panel pages in one module. This illustrative directory shows where those responsibilities go. Add only the files needed by the feature; a module does not need to implement every convention.
src/modules/service/
index.ts # module metadata
acl.ts # permission identifiers
di.ts # service registration
data/entities.ts # stored records
api/ # HTTP routes
backend/ # admin pages
subscribers/ # event handlers
i18n/ # translationsCode and documentation
generators/module-registry.ts · extensibility/current-surfaces.mdx
Why editing a file may not be enough
The application resolves pages, routes and extensions through generated registries. When you add a convention-based file, enable its module and run the generation script provided by your application. Inspect the registry if the feature is missing. Editing the generated output is temporary: the next generation replaces it.
Declaring an entity also affects the database schema. Generate and review its migration, then apply it in the target environment. A successful route build cannot create a missing database table by itself.
Code and documentation
How should modules share information?
Use the documented extension surfaces and declared events for cross-module behavior. Keep the interface small: for example, pass a customer identifier and the data the receiving feature needs. Importing another module’s private implementation makes future changes harder to isolate.
Code and documentation
Before upgrading
Keep your changes in source files and regenerate derived registries. Test the features that depend on changed modules. A module boundary helps organize ownership, but it does not ensure that your customization will survive every upgrade unchanged.
Code and documentation
generators/module-registry.ts · extensibility/current-surfaces.mdx