Module System
Modules are self-contained units of functionality in Fullfinity.
Module Structure
Section titled “Module Structure”modules/my_module/├── __init__.py # Package marker (optional but recommended)├── manifest.yaml # Module metadata (manifest.json also accepted)├── models/ # Model definitions│ ├── __init__.py # MUST list every model: `from . import my_model`│ └── my_model.py├── views/ # UI definitions (.yaml)│ ├── views.yaml # UiView definitions│ ├── actions.yaml # Window actions│ └── menus.yaml # Menu items├── data/ # Seed data (.yaml)│ └── stages.yaml├── security/ # Access control (.yaml)│ └── security.yaml├── templates/ # All templates (.yaml, .jinja)│ ├── report_layout.jinja│ └── section_templates.yaml├── routes/ # API endpoints (optional)│ ├── __init__.py│ └── my_routes.py└── static/ # Static assets (optional) └── js/Folder Purposes
Section titled “Folder Purposes”| Folder | Purpose | File Types |
|---|---|---|
views/ | UI definitions | UiView, WindowAction, UiMenu |
data/ | Seed/reference data | Any model (stages, currencies, categories) |
security/ | Access control | Group, ModelAccess, RecordRule |
templates/ | All templates | WebsiteSectionTemplate, ReportLayout, WebPage |
routes/ | API endpoints | Python controllers |
static/ | Frontend assets | JS, CSS, images |
Manifest File
Section titled “Manifest File”The manifest.yaml defines module metadata (a manifest.json is also accepted —
the loader looks for manifest.yaml first, then manifest.json):
name: CRMidentifier: crmcategory: module_category_crmdescription: Manage leads and pipelinedependencies:- core- meetingsicon: FilterFields
Section titled “Fields”| Field | Type | Description |
|---|---|---|
name | string | Display name |
identifier | string | Unique identifier (snake_case) |
category | string | Module category for grouping |
description | string | Short description |
dependencies | array | Required modules |
icon | string | Tabler icon name |
color | string | Theme color (hex) |
version | string | Semantic version |
Dependencies
Section titled “Dependencies”Modules declare their dependencies:
{ "dependencies": ["core", "contacts"]}- Dependencies are installed automatically
- Circular dependencies are not allowed
coreis always required (implicitly)
Module Loading
Section titled “Module Loading”1. Discovery
Section titled “1. Discovery”On startup, all module directories are scanned:
modules = discover_modules("./modules")# Returns: [ModuleInfo(name="core", ...), ModuleInfo(name="crm", ...)]2. Dependency Resolution
Section titled “2. Dependency Resolution”Modules are sorted by dependencies:
# crm depends on core → core loads firstsorted_modules = topological_sort(modules)3. Loading
Section titled “3. Loading”For each module:
- Import the
models/package — running itsmodels/__init__.py, which explicitly lists every model file viafrom . import <file>lines - Load data files (views, menus, security) from
views/,security/,data/ - Import routes from
routes/directory
Loading conventions
Section titled “Loading conventions”| Folder | File Type | Purpose |
|---|---|---|
models/ | *.py (listed in __init__.py) | Model definitions |
views/ | *.yaml | UI views, actions, menus |
data/ | *.yaml | Seed/default data |
security/ | *.yaml | Access rules and groups |
templates/ | *.yaml | Template definitions |
routes/ | *.py | API endpoints |
:::tip Excluding Files
Python files starting with _ are skipped by the route importer. Use this for:
- Helper modules not meant to be a route file:
_helpers.py - Test utilities:
_test_helpers.py
(Model files are not discovered by name — they load only via the explicit
from . import <file> lines in models/__init__.py. A _-prefixed model helper is
simply omitted from that list.)
:::
File References in YAML
Section titled “File References in YAML”Template files (.jinja, .html, .xml, etc.) can be referenced from YAML:
- data_type: WebsiteSectionTemplate identifier: hero_centered template: hero_centered.jinjaThe loader automatically detects file references based on extension and loads the content.
Models
Section titled “Models”Models are defined in the models/ directory. A model file loads only if it is
listed in models/__init__.py — importing the models package runs its __init__.py,
and the route importer stops at the package (it does not descend into the directory).
A .py file dropped into models/ without a matching from . import <file> line silently
never loads (no registration, no __inherit__/_selection_add extension).
# models/__init__.py — the authoritative, greppable list (keep it alphabetical)from . import leadfrom fullfinity.engine.base import *
class Lead(Model): _verbose_name = "Lead"
name = Char(max_length=255, required=True) email = Char(max_length=255) stage = ManyToOne("CrmStage", related_name="leads") probability = Float(default=10.0):::note Add a model = two edits
Create models/<name>.py and add from . import <name> to models/__init__.py.
There is no manifest models: key, and no by-name auto-discovery.
:::
Views are defined in YAML files in views/:
- data_type: UiView identifier: lead_form_view type: Form model: Lead arch: [...]Actions
Section titled “Actions”Window actions connect views to models. modes is a comma-separated string of view
types; views is the registered list of view records:
- data_type: WindowAction identifier: leads_action name: Leads model: Lead modes: Kanban, List, Form default_view: lead_kanban_viewMenus define navigation:
- data_type: UiMenu identifier: crm_leads_menu name: Leads parent: crm_menu action: leads_action sequence: 10Security
Section titled “Security”Security rules are defined in security/security.yaml:
- data_type: Group identifier: crm_user name: User category: CRM- data_type: ModelAccess identifier: lead_access model: Lead read_perm: true write_perm: true create_perm: true delete_perm: falseInitial Data
Section titled “Initial Data”Seed data can be placed in data/:
- data_type: CrmStage identifier: stage_new name: New sequence: 10 apply_once: true- data_type: CrmStage identifier: stage_qualified name: Qualified sequence: 20 apply_once: trueapply_once Flag
Section titled “apply_once Flag”Controls whether records are updated on module update:
| Value | Behavior |
|---|---|
true | Only create if not exists (preserves user customizations) |
false (default) | Always create/overwrite on update |
Use apply_once: true for user-customizable seed data (stages, categories, currencies).
Omit or set false for system records that must stay in sync (cron jobs).
Module Installation
Section titled “Module Installation”When a module is installed:
- Database Migration - Tables created for new models
- Data Loading - Views, actions, menus, security loaded
- Dependencies - All dependencies installed first
Module Extension
Section titled “Module Extension”Modules can extend other modules:
Extending Models
Section titled “Extending Models”# In your_module/models/contact_ext.py# (remember to add `from . import contact_ext` to your_module/models/__init__.py)from fullfinity.engine.base import *
class ContactExtension(Model): __inherit__ = "Contact" # Extends Contact model
twitter_handle = Char(max_length=100) linkedin_url = Char(max_length=255)Extending Views
Section titled “Extending Views”- data_type: UiView identifier: contact_twitter_extension type: Form model: Contact inherited_view: contact_form_view arch: operations: - action: add target: field: email position: after value: type: field name: twitter_handle properties: widget: TextInputBest Practices
Section titled “Best Practices”- Keep modules focused - One domain per module
- Minimize dependencies - Only depend on what you need
- Use semantic view targeting - Never use index-based targets
- Use
_prefix for non-route Python helpers - Files like_helpers.pyare skipped by the route importer (model files load only viamodels/__init__.py, so just omit them from that list) - Use identifiers - All data should have unique identifiers
Next Steps
Section titled “Next Steps”- Creating Modules - Step-by-step guide
- View Inheritance - Extending views