Skip to content

Module Manifest

The manifest.yaml file defines module metadata and configuration.

name: Human Resources
identifier: hr
category: module_category_hr
description: Employee and contract management
publisher: 'Acme, Inc.'
dependencies:
- core
- contacts
icon: Users
version: '1.0'
requires_engine: 1
FieldTypeDescription
namestringHuman-readable module name
identifierstringUnique identifier (snake_case, no spaces)
publisherstringThe 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
dependenciesarrayList of required module identifiers
requires_engineintegerThe engine major version this app is built for (see Engine compatibility)
FieldTypeDefaultDescription
descriptionstring""Short description shown in module list
categorystringcategory identifier (no default — must resolve to an existing ModuleCategory)Module category for grouping
iconstring""Lucide icon name (without “Icon” prefix)
versionstringThe app’s own version (independent of the engine) — an informational label shown in the app catalogue, not an upgrade-ordering clock
applicationbooleanfalseMarks 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
implicitbooleanfalseAuto-install once all dependencies are installed — the bridge pattern (an explicit uninstall opts back out)
imagestringPath to logo/thumbnail (required for Integration and Theme modules)
python_requirementsarrayExternal 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_categorystringFor 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_methodstringFor integration modules: method name run by the sync cron
static_pathsarrayStatic 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 color key in the manifest is ignored. The “author”/vendor of an app is publisher (there is no separate author key). There are no website or license manifest keys — the loader does not read them.

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.0

The 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_engine differs 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.

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 identifierCategory identifier
module_category_coremodule_category_mrp
module_category_integrationsmodule_category_crm
module_category_salesmodule_category_productivity
module_category_accountingmodule_category_localization
module_category_invoicingmodule_category_hr
module_category_purchasemodule_category_marketing
module_category_inventorymodule_category_products
module_category_websitemodule_category_ecommerce
module_category_themesmodule_category_projects
module_category_toolsmodule_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:

IconName
UsersUsers
Shopping CartShoppingCart
ChartChartBar
CalendarCalendar
MailMail
SettingsSettings
BoxBox
BuildingBuilding
TruckTruck
Credit CardCreditCard
NewspaperNewspaper
GlobeGlobe
Folder KanbanFolderKanban
PalettePalette
dependencies:
- core
- contacts
  • Dependencies are installed automatically before the dependent module
  • Circular dependencies are not allowed
  • core is implicitly required
  1. Modules without dependencies (usually core)
  2. Modules depending only on loaded modules
  3. Repeat until all modules loaded

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.

RoleHow to declare itWhere it appearsHow it’s installed
Application — a standalone, user-facing app (its own menu, its own records)application: trueIn the curated Apps list (the default filter), and browsable by categoryThe user installs it from Apps
Bridge — pure glue that only makes sense when two apps are both presentcategory: module_category_bridges and implicit: trueHidden from the Apps list; visible only when the user clears the filter, grouped under BridgesAuto-installs the instant both apps it joins are present
Add-on / infra — an optional capability layered on one app, or a theme / gateway / base layerneither flagHidden from the Apps listA 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.

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.

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 Bridge
identifier: sale_inventory
category: module_category_bridges
dependencies:
- sales
- inventory
implicit: true

With 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: true module 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 core alone 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.

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.

name: Project Management
identifier: project
category: module_category_projects
description: Manage projects, tasks, and timesheets
dependencies:
- core
- contacts
- hr
icon: FolderKanban
version: '2.1'
  1. Use descriptive names - Clear, concise module names
  2. Choose appropriate icons - Icon should represent the module’s purpose
  3. Minimize dependencies - Only depend on what you actually need