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 YAML; 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: value2Record 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
The loader applies partial updates: on re-import it only writes the fields your record
actually lists, and leaves any column you didn’t mention untouched. So to clear a field on
an already-stored record — e.g. detaching a menu from its parent to promote it to a top-level
app — set it to null explicitly (parent: null); simply deleting the parent: line is a
no-op on the existing row (though a fresh install with no parent: is created top-level).
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.jinjaSupported 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.jinjacontent: ../templates/block.jinja # Loads from parent/templates/layout: layouts/standard.jinja # Loads from ./layouts/Relative Dates (@today / @now)
Section titled “Relative Dates (@today / @now)”Date and Datetime field values may be written as a relative-date token instead of a
fixed calendar date. The loader resolves it at import time against the install date, so seed
and demo data stays anchored to “now” and never goes stale — a database installed today and
one installed a year from now both render a live, non-flat trend in date-grouped reports
instead of every record landing on install-day.
- data_type: SaleOrder identifier: demo_order_acme_001 date: "@today-90d" # 90 days before install validity_date: "@today+7d" # 7 days after installGrammar: an anchor (@today for a date, @now for a datetime) followed by zero or more
signed offsets <+|->N<unit>:
| Unit | Meaning | Example |
|---|---|---|
d | days | @today-45d |
w | weeks | @today-2w |
m | months | @today-3m |
y | years | @today-1y+15d |
h | hours (datetime only) | @now-2h |
@today yields a calendar date; @now yields a UTC timestamp. On a Datetime field,
@today lands at midnight UTC so date-grouped reports bucket cleanly. A malformed token
(e.g. @today-45) fails loudly at import rather than silently resolving to today. A value
that doesn’t start with @ is treated as a literal date exactly as before.
Note: audit fields like
created_dateare stamped by the ORM on create and can’t be set from a data file — back-date those in apost_installhook if a report keys on them.
File Order and Dependencies
Section titled “File Order and Dependencies”Within one module, file order is handled for you. Files are imported in a fixed,
alphabetical order per directory, and a reference to an identifier that hasn’t been
imported yet pulls in the file that declares it first — so menus.yaml referencing an
action declared in actions.yaml resolves regardless of which file is read first. A
reference that no file in the module (or its dependency modules) declares still fails at
import, naming the file, field and identifier.
Use depends when you need to order imports for a reason the loader can’t infer from a
reference — most often a file in another directory whose records must exist first:
- data_type: WindowAction depends: - product_views identifier: products_action name: Products default_view: product_list_viewKey points:
- The value is a list of file names without extension (the loader resolves
.yaml/.yml/.json) - Files listed in
dependsare imported before the current record is processed - Records in other modules are available because those modules install first — list the
module in the manifest’s
dependencies, not independs
Cross-Directory Dependencies
Section titled “Cross-Directory Dependencies”Automatic resolution spans a module’s views/, data/, security/ and templates/
directories, so a menu in views/ referencing a group declared in security/ needs no
depends. The one exception is demo/: it is imported only on a demo database, so a file
outside demo/ never pulls a record out of it — a reference from views/ or data/ to a
demo-only identifier fails, as it must.
depends remains available, and takes relative paths for 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) |
These are ordering hints, not requirements: a reference to an identifier declared anywhere
in the same module resolves on its own. Reach for depends when a file must be imported
first for a reason no reference expresses — for example seed records whose calculate or
create override reads records another file creates.
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: trueData Types
Section titled “Data Types”Groups (Group)
Section titled “Groups (Group)”- data_type: Group identifier: sales_user name: User category: SalesModel 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: falseViews (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: - - R - - product_list_view - product_form_viewMenus (UiMenu)
Section titled “Menus (UiMenu)”- data_type: UiMenu identifier: products_menu name: Products parent: sales_menu action: products_action sequence: 10Custom Models
Section titled “Custom Models”- data_type: CrmStage identifier: stage_new name: New sequence: 10 fold: falseRelationships
Section titled “Relationships”ManyToOne References
Section titled “ManyToOne References”Reference by identifier:
- data_type: Product identifier: product_phone name: Smartphone category: category_electronicsThe 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_menuLoading 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.yaml:
- 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: trueData 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 |
|---|---|---|
link | ["link", "identifier"] | Attach to the existing relations |
unlink | ["unlink", "identifier"] | Detach from the relations |
set | ["set", ["id1", "id2"]] | The relations become exactly these |
clear | ["clear"] | Detach every relation |
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_groupThis 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: 5Example: 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_viewExample: 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_groupReplace vs Add
Section titled “Replace vs Add”Use set 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_groupUse link when extending from another module (adds to what is already there):
- data_type: Group identifier: sales_admin implied_groups: - - L - my_extra_permission_groupWhat 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
["set", [...]]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 ["set", [...]] for M2M fields, not ["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
linkcommand when extending records from other modules - Use Replace for definitions - Use
setcommand 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