App System¶
Apps are the primary way to extend Loom. An app is a directory (or git repo) containing JSON DocType definitions, Rhai scripts, and fixtures.
No Rust toolchain is required to develop or install apps.
App Structure¶
my_app/
├── loom_app.toml # App metadata
├── doctypes/ # DocType definitions
│ ├── employee/
│ │ ├── employee.json # DocType meta
│ │ ├── employee.rhai # Server script
│ │ └── employee.client.js # Client script (optional)
│ └── leave_type/
│ └── leave_type.json
├── api/ # Whitelisted API methods
│ └── get_leave_balance.rhai
├── hooks.toml # Scheduled tasks, doc events, queues
├── fixtures/ # Seed data
│ └── leave_type.json
├── plugins/ # Reserved for future WASM plugins
└── frontend/ # Optional frontend extensions
└── src/
loom_app.toml¶
[app]
name = "my_app"
version = "1.0.0"
title = "My Application"
description = "A custom Loom app"
modules = ["HR", "Payroll"]
icon = "briefcase" # Icon shown in the app switcher and workspace
color = "#4f46e5" # Accent color for the app workspace
| Field | Required | Description |
|---|---|---|
name |
yes | App identifier (snake_case) |
version |
yes | Semver version string |
title |
yes | Human-readable app name |
description |
no | Short description |
modules |
yes | List of modules this app provides |
icon |
no | Icon name for app switcher and workspace header |
color |
no | Hex color used as the app's accent color |
hooks.toml¶
# Scheduled tasks
[[scheduler]]
cron = "0 */6 * * *"
method = "my_app.sync_data"
# Document events
[[doc_events]]
doctype = "Employee"
event = "on_update"
method = "scripts/on_employee_update.rhai"
# Custom queues
[queues]
names = ["default", "long", "critical"]
# Fixture declarations (for loom export-fixtures)
[[fixtures]]
doctype = "Role"
[[fixtures]]
doctype = "Leave Type"
filters = { module = "HR" }
# Plugin pages (custom frontend routes)
[[pages]]
route = "/app/org-chart"
label = "Org Chart"
component = "OrgChart"
# Workspace sidebar entries
[[workspace]]
label = "HR"
icon = "users"
[[workspace]]
label = "Payroll"
icon = "wallet"
# Dashboard widgets (appear on the app workspace)
[[dashboard]]
type = "shortcut"
label = "New Employee"
link = "/app/employee/new"
icon = "user-plus"
[[dashboard]]
type = "number"
label = "Active Employees"
doctype = "Employee"
filters = { status = "Active" }
color = "#10b981"
[[dashboard]]
type = "chart"
label = "Monthly Headcount"
doctype = "Employee"
chart_type = "line"
field = "creation"
span = "monthly"
color = "#6366f1"
[[dashboard]]
type = "chart"
label = "Department Distribution"
doctype = "Employee"
chart_type = "donut"
field = "department"
color = "#f59e0b"
Workspace Entries¶
The [[workspace]] section defines sidebar entries for the app. Each entry appears in the context-aware sidebar when the user is inside the app.
| Field | Required | Description |
|---|---|---|
label |
yes | Display name in the sidebar |
icon |
no | Icon name for the sidebar entry |
Dashboard Widgets¶
The [[dashboard]] section defines widgets shown on the app's workspace home page. Three widget types are supported:
| Type | Description |
|---|---|
shortcut |
Quick-link button with a label, link, and optional icon |
number |
Number card showing a count of documents matching filters |
chart |
Chart visualization of document data |
Shortcut fields:
| Field | Required | Description |
|---|---|---|
label |
yes | Button text |
link |
yes | URL to navigate to |
icon |
no | Icon name |
Number card fields:
| Field | Required | Description |
|---|---|---|
label |
yes | Card title |
doctype |
yes | DocType to count |
filters |
no | Filter criteria |
color |
no | Hex color for the card |
Chart fields:
| Field | Required | Description |
|---|---|---|
label |
yes | Chart title |
doctype |
yes | DocType to query |
chart_type |
yes | bar, line, or donut |
field |
yes | Field to aggregate on |
span |
no | Time grouping: daily, weekly, monthly, yearly (for bar/line charts) |
direction |
no | vertical (default) or horizontal (bar charts only) |
color |
no | Hex color for the chart |
Installing an App¶
# From a git repo
loom get-app https://github.com/someone/my_app
# From a local directory (already cloned)
# Just place it in the apps/ directory
# Install into the site
loom --site mysite.localhost install-app my_app
What happens on install-app:¶
- Reads
loom_app.toml→ registers the app - Loads all
doctypes/*.json→ inserts into DocType registry - Loads all
*.rhaiscripts → stores in__scripttable - Loads
*.client.js→ stores in__customizationtable - Runs auto-migration → creates/alters database tables
- Loads
fixtures/*.json→ inserts seed data - Builds frontend extensions if present
Client Scripts¶
Client scripts add client-side behavior to DocTypes — custom buttons, validation, and field change handlers. Place a .client.js file next to the DocType JSON:
my_app/doctypes/todo/
├── todo.json
├── todo.rhai # Server script
└── todo.client.js # Client script
Example: todo.client.js¶
// Validation — return an error string to block save
loom.validate = function(doc) {
if (doc.due_date && new Date(doc.due_date) < new Date().setHours(0,0,0,0)) {
return "Due Date cannot be in the past";
}
};
// Custom button on the form view only
loom.add_button("Mark Complete", function(doc) {
doc.status = "Completed";
}, { variant: "primary", view: "form" });
// Custom button on the list view only
loom.add_button("Close Selected", function(selectedRows) {
if (!selectedRows || selectedRows.length === 0) {
alert("Select rows first");
return;
}
alert("Closing " + selectedRows.length + " todo(s)");
}, { view: "list" });
See Scripting > Client Scripts for the full API reference.
Developer Mode¶
When developer_mode is enabled in site_config.json, Loom watches apps/ and core_doctypes/ for file changes:
- Saving a
.jsonDocType file reloads and migrates it instantly - Saving a
.rhaiscript reloads it into the hook runner - Saving a
.client.jsfile updates the__customizationtable
This means you can edit files in your editor and see changes immediately without restarting the server.
Customizing 3rd Party Apps¶
You can override permissions and field properties of installed apps without modifying the app's files:
- Permissions: Use the Role Permission Manager (
/app/role-permission-manager) - Field properties: Use Customize Form (
/app/customize-form/DocTypeName) - Client scripts: Add via Customize Form
Overrides are stored in __customization and persist across app updates.