Data Files
Data files load initial records when a module is installed.
File format: YAML is the convention. Every shipped module writes its data files as
.yaml(the loader also accepts.json/.yml). The examples below are shown in JSON for readability, but in practice use.yamlfiles — the record shape (data_type,identifier, fields, command arrays) is identical in both formats.
File Location
Section titled “File Location”Place data files (.yaml) in the appropriate directories — no manifest data: key is
needed; the loader auto-discovers every .yaml/.yml/.json file (except those starting
with _) under views/, data/, security/, and templates/ (plus demo/ in demo mode):
modules/your_module/├── views/│ ├── views.yaml # UI views (UiView)│ ├── actions.yaml # Window actions (WindowAction)│ └── menus.yaml # Menus (UiMenu)├── data/│ ├── stages.yaml # Seed data (user-customizable)│ └── categories.yaml # Reference data├── security/│ └── security.yaml # Groups and permissions└── templates/ ├── section_templates.yaml # Template definitions ├── hero.jinja # Jinja template files └── report_layout.jinja # Report templatesFolder Purposes
Section titled “Folder Purposes”| Folder | Purpose | Record 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 |
Files starting with _ are ignored (e.g., _draft.json).
Structure
Section titled “Structure”Data files are arrays of record definitions (a YAML list, or a JSON array):
[ { "data_type": "ModelName", "identifier": "unique_identifier", "field1": "value1", "field2": "value2" }]Record Fields
Section titled “Record Fields”Required Fields
Section titled “Required Fields”| Field | Description |
|---|---|
data_type | Model class name |
identifier | Unique identifier for updates |
apply_once Flag
Section titled “apply_once Flag”Controls whether records are updated on module update:
{ "data_type": "CrmStage", "identifier": "crm_stage_new", "name": "New", "sequence": 1, "apply_once": true}| Value | Behavior |
|---|---|
true | Only create if not exists (preserves user customizations) |
false (default) | Always create/overwrite on update |
When to use apply_once: true:
- Seed data users may customize (stages, categories, currencies, payment terms)
- Reference data with user-editable fields
When to use apply_once: false (or omit):
- System records that must stay in sync (cron jobs)
- UI definitions (views, actions, menus) - these are managed by the module system
- Security records (groups, model access) - should always match code
File References
Section titled “File References”Template files can be referenced from JSON by filename. The loader auto-detects file references based on known template extensions:
{ "data_type": "WebsiteSectionTemplate", "identifier": "hero_centered", "template": "hero_centered.jinja"}Supported extensions: .jinja, .jinja2, .html, .xml, .txt, .md, .css, .js
How it works:
- String fields with supported template extensions
- That don’t start with
http://,https://, or/ - Are treated as file references relative to the JSON file
- The file content is loaded into the field
Examples:
{ "template": "hero.jinja", // Loads ./hero.jinja "content": "../templates/block.jinja", // Loads from parent/templates/ "layout": "layouts/standard.jinja" // Loads from ./layouts/}File Dependencies
Section titled “File Dependencies”Use depends to ensure referenced records exist before processing:
{ "data_type": "WindowAction", "depends": ["product_views"], "identifier": "products_action", "name": "Products", "default_view": "product_list_view"}Key points:
- Use
dependswhen a record references another record by identifier (e.g.,default_view,search_view,action,parent,groups) - The value is a list of file names without extension (the loader resolves
.yaml/.yml/.json) - Files are imported in dependency order before the current record is processed
Cross-Directory Dependencies
Section titled “Cross-Directory Dependencies”Use relative paths to reference files in other directories:
{ "data_type": "UiMenu", "depends": ["actions", "../security/security"], "identifier": "admin_menu", "name": "Admin Menu", "action": "admin_action", "groups": [["L", "my_module_admin"]]}| Path Format | Description |
|---|---|
"actions" | Same directory (views/actions.yaml) |
"../security/security" | Parent + subdirectory (security/security.yaml) |
"../data/seed_data" | Parent + subdirectory (data/seed_data.yaml) |
Common patterns:
| File | Typical Dependencies |
|---|---|
actions.json | View files (["model_views", "other_views"]) |
menus.json | Action files and security (["actions", "../security/security"]) |
| View inheritance files | Base view files |
| Data files with groups | Security files (["../security/security"]) |
Common Fields
Section titled “Common Fields”All other fields map to model fields:
{ "data_type": "Product", "identifier": "product_laptop", "name": "Laptop", "price": 999.99, "active": true}Data Types
Section titled “Data Types”Groups (Group)
Section titled “Groups (Group)”{ "data_type": "Group", "identifier": "sales_user", "name": "User", "category": "Sales"}Model Access (ModelAccess)
Section titled “Model Access (ModelAccess)”{ "data_type": "ModelAccess", "identifier": "product_access", "name": "Product Access", "model": "Product", "group": "sales_user", "read_perm": true, "write_perm": true, "create_perm": true, "delete_perm": false}Views (UiView)
Section titled “Views (UiView)”{ "data_type": "UiView", "identifier": "product_list_view", "name": "product_list_view", "type": "List", "model": "Product", "arch": [...]}Window Actions (WindowAction)
Section titled “Window Actions (WindowAction)”{ "data_type": "WindowAction", "identifier": "products_action", "name": "Products", "model": "Product", "modes": "List,Form", "views": ["product_list_view", "product_form_view"]}Menus (UiMenu)
Section titled “Menus (UiMenu)”{ "data_type": "UiMenu", "identifier": "products_menu", "name": "Products", "parent": "sales_menu", "action": "products_action", "sequence": 10}Custom Models
Section titled “Custom Models”{ "data_type": "CrmStage", "identifier": "stage_new", "name": "New", "sequence": 10, "fold": false}Relationships
Section titled “Relationships”ManyToOne References
Section titled “ManyToOne References”Reference by identifier:
{ "data_type": "Product", "identifier": "product_phone", "name": "Smartphone", "category": "category_electronics"}The system resolves category_electronics to the actual record ID.
Parent Menu Reference
Section titled “Parent Menu Reference”{ "data_type": "UiMenu", "identifier": "sub_menu", "name": "Sub Menu", "parent": "main_menu"}Loading Order
Section titled “Loading Order”- Security groups (
Group) - Model access rules (
ModelAccess) - Views (
UiView) - Actions (
WindowAction) - Menus (
UiMenu) - Other data (sorted by dependencies)
Update Behavior
Section titled “Update Behavior”When a module is updated:
- Records with existing identifiers are updated
- New identifiers create new records
- Removed identifiers keep existing data (no automatic deletion)
Example: Complete Data File
Section titled “Example: Complete Data File”modules/crm/data/stages.json:
[ { "data_type": "CrmStage", "identifier": "stage_new", "name": "New", "sequence": 10, "probability": 10, "fold": false }, { "data_type": "CrmStage", "identifier": "stage_qualified", "name": "Qualified", "sequence": 20, "probability": 30, "fold": false }, { "data_type": "CrmStage", "identifier": "stage_proposition", "name": "Proposition", "sequence": 30, "probability": 60, "fold": false }, { "data_type": "CrmStage", "identifier": "stage_won", "name": "Won", "sequence": 100, "probability": 100, "fold": true }, { "data_type": "CrmStage", "identifier": "stage_lost", "name": "Lost", "sequence": 110, "probability": 0, "fold": true }]Data Inheritance
Section titled “Data Inheritance”When your module needs to modify records defined by another module (e.g., adding groups to an existing menu, adding implied_groups to a group), use the same identifier with command arrays for M2M fields.
How It Works
Section titled “How It Works”- Same identifier = update existing record
- Only specified fields are modified
- Command arrays control how M2M/O2M fields are updated
Command Types for M2M Fields
Section titled “Command Types for M2M Fields”| Command | Syntax | Description |
|---|---|---|
L (Link) | ["L", "identifier"] | Add to existing relations |
U (Unlink) | ["U", "identifier"] | Remove from relations |
R (Replace) | ["R", ["id1", "id2"]] | Replace all relations |
A (Unlink All) | ["A"] | Remove all relations |
Example: Add Implied Groups
Section titled “Example: Add Implied Groups”Your module can add implied groups to an existing group:
{ "data_type": "Group", "identifier": "sales_manager_group", "implied_groups": [["L", "my_new_group"], ["L", "another_group"]]}This adds groups to the existing implied_groups (doesn’t replace).
Example: Modify a Menu
Section titled “Example: Modify a Menu”Change an existing menu’s parent or sequence:
{ "data_type": "UiMenu", "identifier": "crm_pipeline_menu", "parent": "my_module_root_menu", "sequence": 5}Example: Add Views to an Action
Section titled “Example: Add Views to an Action”Add your custom view to an existing action:
{ "data_type": "WindowAction", "identifier": "pipeline_action", "views": [["L", "my_custom_pipeline_view"]]}Example: Add Group Restrictions to a Menu
Section titled “Example: Add Group Restrictions to a Menu”Restrict an existing menu to specific groups:
{ "data_type": "UiMenu", "identifier": "admin_menu", "groups": [["L", "my_admin_group"]]}Replace vs Add
Section titled “Replace vs Add”Use R (Replace) when defining the complete set in the original module:
{ "data_type": "Group", "identifier": "sales_admin", "name": "Sales Administrator", "implied_groups": [["R", ["sales_user_group", "crm_user_group"]]]}Use L (Link) when extending from another module (adds to existing):
{ "data_type": "Group", "identifier": "sales_admin", "implied_groups": [["L", "my_extra_permission_group"]]}What Can Be Extended?
Section titled “What Can Be Extended?”Any record type with an identifier can be extended:
| Record Type | Common Extensions |
|---|---|
Group | Add implied_groups, change category |
UiMenu | Change parent, sequence, action, add groups |
WindowAction | Add views, modify context |
ModelAccess | Change permissions |
RecordRule | Modify domain |
| Seed data | Modify defaults |
Scaffolding a Module
Section titled “Scaffolding a Module”To scaffold a new module skeleton (directories, manifest, __init__.py), use:
fullfinity-server new my_module# --path <dir> to choose where it is created (default: current directory)Record extensions are then written by hand in the new module’s data files — add a record
with the same identifier as the record you want to extend (see the command-array
examples above).
Uninstall Behavior
Section titled “Uninstall Behavior”When a module is uninstalled:
- Records owned by the module are deleted - only records where
modulefield matches the uninstalling module - Dependencies are updated - all parent modules are updated to restore their original state
- Extensions are automatically reverted - because parent modules re-import their JSON files with
["R", [...]]commands
This means:
- Scalar field changes made by the uninstalled module are restored
- M2M links added by the uninstalled module are removed
- ManyToOne references to deleted records are restored to originals
Important: For this to work correctly, original modules should use ["R", [...]] (Replace) for M2M fields, not ["L", ...] (Link).
Best Practices
Section titled “Best Practices”- Use descriptive identifiers -
product_laptopnotprod1 - Group related data - Separate files for stages, tags, etc.
- Order matters - Dependencies must be defined first
- Immutable identifiers - Never change identifiers after release
- Keep data minimal - Only include essential seed data
- Use Link for extensions - Use
Lcommand when extending records from other modules - Use Replace for definitions - Use
Rcommand when defining the complete set in your own module
Next Steps
Section titled “Next Steps”- Creating Modules - Full module tutorial
- Security - Groups and permissions
- View Inheritance - Extending views