Module Manifest
The manifest.yaml file defines module metadata and configuration.
Basic Structure
Section titled “Basic Structure”name: Human Resourcesidentifier: hrcategory: module_category_hrdescription: Employee and contract managementpublisher: 'Acme, Inc.'dependencies:- core- contactsicon: Usersversion: '1.0'requires_engine: 1Fields Reference
Section titled “Fields Reference”Required Fields
Section titled “Required Fields”| Field | Type | Description |
|---|---|---|
name | string | Human-readable module name |
identifier | string | Unique identifier (snake_case, no spaces) |
publisher | string | The organization that publishes the app (the “author”/vendor). Shown to users and part of the App Store release identity — identifier + publisher + version. In the store it is a display value only: the verified publisher that owns the identifier is authoritative |
dependencies | array | List of required module identifiers |
requires_engine | integer | The engine major version this app is built for (see Engine compatibility) |
Optional Fields
Section titled “Optional Fields”| Field | Type | Default | Description |
|---|---|---|---|
description | string | "" | Short description shown in module list |
category | string | category identifier (no default — must resolve to an existing ModuleCategory) | Module category for grouping |
icon | string | "" | Lucide icon name (without “Icon” prefix) |
version | string | — | The app’s own version (independent of the engine) — an informational label shown in the app catalogue, not an upgrade-ordering clock |
application | boolean | false | Marks a standalone, user-facing app (see Module roles). Only application: true modules appear in the curated Apps list users browse; bridges, add-ons, localization, themes and infra stay out of it |
implicit | boolean | false | Auto-install once all dependencies are installed — the bridge pattern (an explicit uninstall opts back out) |
image | string | — | Path to logo/thumbnail (required for Integration and Theme modules) |
python_requirements | array | — | External PyPI distributions the module imports (e.g. ["requests", "pandas>=2.0"]). The engine never installs these — it only verifies each is importable at install time and refuses the install with a clear error if one is missing. Provide them in the environment, or vendor the code into the module. |
integration_category | string | — | For integration modules: which catalog category the provider files under (Online Payment, Messaging, Shipping, Banking, Email, Calendar, Marketplace, E-Invoicing). See Integrations — a blank one leaves the provider in an unnamed group |
cron_sync_method | string | — | For integration modules: method name run by the sync cron |
static_paths | array | — | Static subdirectories under static/ to serve (e.g. [css, js, images]) |
The module’s display color is computed deterministically from its name at load time; a
colorkey in the manifest is ignored. The “author”/vendor of an app ispublisher(there is no separateauthorkey). There are nowebsiteorlicensemanifest keys — the loader does not read them.
Engine compatibility
Section titled “Engine compatibility”Every app declares the single engine major version it targets via requires_engine (an
integer, e.g. 1). The platform follows SemVer: backward compatibility is guaranteed within a
major version and may break across one — so same-major is the whole compatibility test.
requires_engine: 1 # built for engine 1.x — runs on 1.0, 1.4, 1.9 …; refused on 2.0The declared major is checked against the running engine at two points, and a mismatch is refused (it does not warn-and-load — an incompatible app can fail to compose):
- Install — installing an app (or any dependency it pulls in) whose
requires_enginediffers from the running engine’s major is rejected with a clear error. - Upgrade — after the engine itself is upgraded, every installed app is re-checked; an app that has not shipped a build declaring the new major blocks the upgrade until it is updated.
When the engine releases a new major, an app must ship a new build that bumps its
requires_engine to match. Because compatibility holds across every minor/patch within a major,
an app built for 1 needs no change as the engine moves through 1.1, 1.2, and so on.
requires_engine is distinct from version (the app’s own version, which moves on the app’s own
schedule). The build-time manifest gate (fullfinity-server check --only manifests) requires
requires_engine to be a positive integer on every shipped module.
Categories
Section titled “Categories”category must be one of the identifiers below (the loader rejects any other value,
and the build-time manifest gate fails the build). Categories are display groupings in the
Apps browser’s Category facet:
| Category identifier | Category identifier |
|---|---|
module_category_core | module_category_mrp |
module_category_integrations | module_category_crm |
module_category_sales | module_category_productivity |
module_category_accounting | module_category_localization |
module_category_invoicing | module_category_hr |
module_category_purchase | module_category_marketing |
module_category_inventory | module_category_products |
module_category_website | module_category_ecommerce |
module_category_themes | module_category_projects |
module_category_tools | module_category_misc |
module_category_bridges |
Give every bridge the module_category_bridges category so it groups
cleanly when a user browses all modules (the curated Apps list hides bridges regardless
of category — see below).
Icons use Lucide Icons. Use the icon name without any prefix. Common examples:
| Icon | Name |
|---|---|
| Users | Users |
| Shopping Cart | ShoppingCart |
| Chart | ChartBar |
| Calendar | Calendar |
Mail | |
| Settings | Settings |
| Box | Box |
| Building | Building |
| Truck | Truck |
| Credit Card | CreditCard |
| Newspaper | Newspaper |
| Globe | Globe |
| Folder Kanban | FolderKanban |
| Palette | Palette |
Dependencies
Section titled “Dependencies”Basic Dependencies
Section titled “Basic Dependencies”dependencies:- core- contactsDependency Resolution
Section titled “Dependency Resolution”- Dependencies are installed automatically before the dependent module
- Circular dependencies are not allowed
coreis implicitly required
Order of Loading
Section titled “Order of Loading”- Modules without dependencies (usually
core) - Modules depending only on loaded modules
- Repeat until all modules loaded
Module roles
Section titled “Module roles”Every module plays exactly one of three roles. Choosing the right one is what keeps the Apps browser a curated list of real applications instead of a wall of 150 packages.
| Role | How to declare it | Where it appears | How it’s installed |
|---|---|---|---|
| Application — a standalone, user-facing app (its own menu, its own records) | application: true | In the curated Apps list (the default filter), and browsable by category | The user installs it from Apps |
| Bridge — pure glue that only makes sense when two apps are both present | category: module_category_bridges and implicit: true | Hidden from the Apps list; visible only when the user clears the filter, grouped under Bridges | Auto-installs the instant both apps it joins are present |
| Add-on / infra — an optional capability layered on one app, or a theme / gateway / base layer | neither flag | Hidden from the Apps list | A Configuration toggle on the parent app, or pulled in as a dependency |
These roles are mutually exclusive and gate-enforced — the build-time manifest gate
rejects application: true combined with implicit: true, application: true on a
module_category_bridges module, or a module_category_bridges module that is not
implicit: true.
Optional feature toggles
Section titled “Optional feature toggles”An add-on is enabled from the parent app rather than browsed. Declare a
Boolean(installs_app="<module>") field on the parent app’s Configuration model and the
framework installs/uninstalls the add-on when the switch flips — no handler code needed:
class ConfigurationInventory(Model): __inherit__ = "Configuration"
enable_landed_costs = Boolean( installs_app="landed_cost", description="Landed Costs", hint="Allocate freight and duty onto received goods.", )Use a toggle when the add-on is a real capability choice the user opts into (e.g. dropshipping, quality control). Do not wrap a plain bridge in a toggle — connecting two apps the user already installed is not a decision worth a switch; make it a bridge instead.
Bridges (implicit auto-install)
Section titled “Bridges (implicit auto-install)”A bridge carries both category: module_category_bridges and implicit: true, so it
auto-installs the moment all of its dependencies are installed — without ever being
explicitly selected.
name: Sales - Inventory Bridgeidentifier: sale_inventorycategory: module_category_bridgesdependencies: - sales - inventoryimplicit: trueWith the manifest above, sale_inventory stays uninstalled while only sales is
present, and installs automatically the instant inventory is also installed (and
vice-versa). You never tick it in the module list.
How it works:
- After any install / module-list refresh, the engine scans every
implicit: truemodule that is Not Installed and installs those whose entire dependency list is already satisfied. - It cascades. Auto-installing one implicit module re-runs the scan, so an implicit module whose deps are only completed by another implicit install gets picked up in the same pass.
- It is purely additive — implicit modules are never auto-uninstalled when a dependency is later removed; that follows normal uninstall rules.
- Uninstalling one on purpose sticks. The scan runs on every install and every upgrade, so “auto-install when eligible” would otherwise mean “reinstall forever” — a removal would last only until the next upgrade. An explicit uninstall therefore records the decision, and the scan skips that module until someone installs it again. A removal that is a consequence rather than a decision does not count: a dependent taken down alongside what it depends on, or an enterprise addon dropped when a licence lapses, both come back once eligible again.
- A module that depends on
corealone therefore installs on every database, as part of database creation (the scan runs once core itself is fully imported, so the module’s security, views and menus resolve against a complete database). That is how a platform-wide capability with no app to hang off — the Integrations catalog — ships without asking anyone to install it.
Version
Section titled “Version”The version key is a free-text label (shipped modules use values like '1.0'). It is
informational only. Schema/data migrations are not driven by any version — they replay
off an append-only change-id ledger (see
Migrations & Upgrades), so this key does not affect upgrades.
Complete Example
Section titled “Complete Example”name: Project Managementidentifier: projectcategory: module_category_projectsdescription: Manage projects, tasks, and timesheetsdependencies:- core- contacts- hricon: FolderKanbanversion: '2.1'Best Practices
Section titled “Best Practices”- Use descriptive names - Clear, concise module names
- Choose appropriate icons - Icon should represent the module’s purpose
- Minimize dependencies - Only depend on what you actually need
Next Steps
Section titled “Next Steps”- Data Files - Load initial data
- Creating Modules - Full module tutorial