Every tool, in one place
This page is the practical reference for each tool in the sidebar of your Archestack environment. Each section covers what the tool does, when you'd reach for it, the steps for the most common workflows, and the gotchas that catch new users. The final section (Script-callable APIs) documents the exact surface area scripts can call.
Schema Designer
A visual editor for the database schema, tables, columns, types, relationships, indexes, that auto-saves to a JSON document. The schema is the source of truth for your application; no other tool sees a column until you add it here and deploy.
Common workflow: in the toolbar, click Add Table (icon button). A
dialog titled "Create New Table" opens; type a name (the helper text reminds you "Lowercase
with underscores recommended"), click Create. The table appears on the canvas with
an auto-generated id SERIAL primary key. Select the table, the right panel
opens on the Columns tab. Add columns from the dashed-border "Add column" box at the
bottom (name field, Type dropdown, click Add Column). Click each column to expand
and adjust toggles (PK, NULL, UQ), Length, and
Default value.
Column types available in the Type dropdown: SERIAL, BIGSERIAL, INTEGER, BIGINT, SMALLINT, DECIMAL, NUMERIC, REAL, DOUBLE PRECISION, VARCHAR, CHAR, TEXT, BOOLEAN, DATE, TIME, TIMESTAMP, TIMESTAMPTZ, UUID, JSON, JSONB, BYTEA, INET, CIDR, MACADDR, MONEY, INTERVAL, plus geometric and array types. Length is only relevant for VARCHAR and CHAR.
Foreign keys live in the Relations tab of the right panel (not "Relationships"). Scroll to "Add relationship": pick the FK column on the current table, the relation type (One-to-One / One-to-Many / Many-to-Many), the references table, the references column, and click Add Relationship. Each relationship has On Delete and On Update dropdowns (CASCADE / SET NULL / SET DEFAULT / RESTRICT / NO ACTION).
Auto-save: the indicator in the bottom-left flicks between "Auto saving…" and "Saved". There's also a Save button in the toolbar (only enabled when there are pending changes) for explicit saves.
Other toolbar buttons: Add Group (visual containers for related tables, properties panel titled "Group"), Add Text (free-form annotations, properties panel titled "Text Annotation"), zoom controls, package filter, table search.
Gotchas: renaming a column produces a destructive plan (drop + add), edit
the SQL on the deployment's Generated SQL tab to use RENAME COLUMN if you want
to preserve data. Adding a NOT NULL column to a non-empty table without a default fails,
make it nullable, backfill, then alter. The Deploy toolbar button takes you to a
deployment config page, not directly into a deploy.
Database Deployments
Lists every deployment with its status (Draft / Executing / Succeeded / Failed) and runs
new ones. Route: /database-deployments/overview.
Top-right buttons: Refresh (reload the list), New Deployment (create a new draft).
From Schema Designer: the green Deploy rocket-icon button in the
Schema Designer toolbar takes you to /database-deployments/configure/new, a fresh
deployment configuration page.
Deployment configuration page: two tabs.
- Configuration - pre/post-deployment script editors (PostgreSQL) with a Test Run button each. Pre-script runs before schema changes; post-script after. Useful for backfills, index rebuilds, or staging-area cleanup. Plus a Force Deploy (Allow Data Loss) toggle for destructive operations.
- Generated SQL - shows the migration the platform produced. The SQL is editable. A Regenerate button (refresh icon) re-runs the diff if you've made schema changes since opening this page.
Top buttons: Back, Save (saves the configuration as a Draft), Deploy (runs the deployment immediately). Deploy is what you click last.
Gotchas: deployments aren't automatically wrapped in a transaction, a
failure halfway through leaves the database in a partial state. For risky migrations, wrap
your statements in BEGIN; … COMMIT; in the pre-script. The Force Deploy
toggle skips safety checks; use it deliberately.
Business Entities
Curated views built on a master table plus joins from related tables. Pages bind to Business Entities, never to raw tables. See Concepts → Business Entities for the why.
Common workflow: click Create. The full-page editor opens with tabs Visual, JSON, Events. Set Entity Name, Master Table (Autocomplete), and Label Column (the column whose value represents a record in pickers). Save, the BE auto-generates a list of native columns from the master table.
Adding joins: scroll to the Join Configurator card and click Add Join. Each join opens as an Accordion with: Join Type (INNER / LEFT / RIGHT), From Table, To Table (Autocomplete grouped by Database Tables / Third Party), From Column, To Column, optional Type Cast dropdowns, and a column picker for which target columns to surface.
Aggregations: on a join, toggle Aggregate Mode. For each picked column you then choose an Aggregate Function: COUNT / SUM / AVG / MIN / MAX / COUNT DISTINCT. "Number of open contacts on this Company" is the canonical example.
Run Preview: the editor has a Run Preview button (with a play icon) that executes the full join configuration and shows real rows in a data grid. If a join column comes back empty, the column wasn't ticked in the join's column picker (or the relationship is misconfigured).
Multiple BEs per master table: nothing stops you from having
customer (sales view) and customer (support view) on the same
master. Different pages, different audience-appropriate columns, same underlying row.
Gotchas: joined and aggregated columns are read-only. To edit the joined label, navigate to the source record. Aggregated columns are recomputed on every query, fine for hundreds of rows, slow on millions.
Page Editor
Configures the runtime UI by binding a Page to a Business Entity. The platform auto-generates sections based on the BE's columns; you tune the layout from there. There is no separate "template picker", every page starts from the same generated baseline and you customise it.
Common workflow: click Create. Fill Page Name,
Page Route (e.g. /companies), pick the Business Entity
(Autocomplete). The page opens in the editor with tabs Visual, Overview,
Create, Entities, Events, JSON. Tweak the auto-generated
layout, then flip the Published Switch in the top header to ON.
Field widget types (the Type Select on each field): Text,
Textarea, Number, Date, Select, Checkbox, Email. Use Select for foreign-key
fields and set the field's Entity autocomplete to the referenced BE so users pick
by label.
Tabs in the detail form: click Add Tab in the Visual tab. Inside a
tab you can add a RelatedGrid section bound to another BE filtered by the current
record's PK. Classic example: a Company page with "Contacts" and "Deals" tabs, each filtered
by company_id = current company's id.
Action buttons on the detail form's Header inspector. Click Add in the Actions section. Each action has Label, Icon (Save / Delete / Add / Refresh / Download / Bolt / None), Variant (Contained / Outlined / Text), Color (Primary / Secondary / Error / Success / Warning / Info), and Steps (an ordered list of: Save Record, Delete Record, Navigate, Business Events, Custom). To run a Business Event from a button, set the step type to Business Events and pick the event by name.
Publishing: the Published Switch in the top header is the only publish control. ON = the page appears under Published Pages in the sidebar and end users navigating to its route see it. OFF = draft (only you in the editor see your changes).
Gotchas: the auto-generated columns on a fresh Page mirror the BE, if the BE changes (you add a join), the Page won't auto-pick up new columns. Reload the page in the editor or add the column manually to the section. There is currently no "Generate PDF" action step type, to wire a PDF to a page button, expose the PDF via a Script Module that calls the REST endpoint and use a Custom step (or call the endpoint directly from a Frontend Template).
Business Events
Rules that fire on data changes (or on a schedule, or manually) and run actions when conditions are met. The platform's "when X happens, do Y" mechanism. See Concepts → Business Events for the model.
Common workflow: click Create. Set Rule Name, toggle Enabled. Pick Business Entity. Tick one or more Triggers: BeforeCreate / BeforeUpdate / AfterCreate / AfterUpdate / BeforeDelete / OnSchedule / Manual / InitialValue. Build the condition tree (AND/OR groups). Add one or more actions in the FlowCanvas. Save.
Action types (RuleActionKind):
- Execute Script - run a C# script. The most flexible action; reach for it whenever you want to set a field, do a calculation, call an API, anything custom. The script gets
Entity(mutate it on Before* triggers to change the in-flight write),OldEntity,Log,Db,Modules. - Validate - a script that returns
bool.truemeans validation fails and the save is blocked with the configured error message. Optionally highlights specific columns via errorColumns. - Block Operation - hard-stops the operation with a configured message. No script.
- Create Entity - inserts a record into another BE. Field values support template expressions like
{{ Entity.column_name }}and{{ now() }}. - Update Entity - updates records in another BE matching a condition filter. Same template expressions.
- Delete Entity - deletes records matching a condition. Refuses to fire without a condition (safety).
- Send Email / Send Webhook / Publish Event - defined in the schema as future enhancements; currently logged and skipped.
"Set a field" pattern: there is no discrete Set Field action. The
way to set a field is an Execute Script on a Before* trigger that mutates
Entity. Example: Entity.title = ((string)Entity.title)?.Trim();. The platform
persists the modified Entity as part of the in-flight write, no extra query, no recursion
risk.
Run Simulation: the trigger editor's Simulate tab has a record picker and a Run Simulation button. The platform runs your conditions and actions against the chosen record without persisting changes, write operations execute in a transaction that gets rolled back. The output panel shows which conditions matched and what each action would have done. Use this to catch misconfigurations before live data hits them.
Template expressions in action configuration use {{ … }}
braces. The actual tokens: {{ Entity.column }}, {{ OldEntity.column }},
{{ now() }} / {{ getdate() }}, {{ today() }},
{{ guid() }} / {{ newid() }}, {{ year() }},
{{ month() }}, {{ day() }}, {{ timestamp() }},
aggregates like {{ SUM(column) }} within join contexts, and post-substitution
arithmetic like {{ Entity.quantity * Entity.price }}. There is
no {{ user.email }} token.
Condition operators: Equals, NotEquals, GreaterThan, LessThan, GreaterThanOrEqual, LessThanOrEqual, Like (= ILIKE), Contains (= ILIKE %value%), IsNull, IsNotNull, In (= ANY(...)).
Gotchas: a trigger that mutates the same record it watches will re-fire
itself if you use Update Entity; use Before Update + Execute Script
+ Entity.field = … instead. Two triggers on the same event fire in priority
order. Disabled events still appear in the list, the Enabled toggle is separate
from the trigger config.
Script Modules
Reusable C# scripts compiled at runtime by Roslyn. Take parameters, query the database via
the Db helper, return a value. Callable from Business Events, Scheduled Events,
and directly from the front end.
Common workflow: click Create. Editor opens with tabs Edit,
Parameters, Test, JSON. Name the module (PascalCase, e.g.
RecalcOpenRevenue). In the Parameters tab, click Add per
parameter (Name, Type, Required, Default Value). Type options: string, int, decimal, double,
bool, DateTime. Back to the Edit tab, write C#. IntelliSense is on.
Test panel: the Test tab shows your parameter inputs on the left and a Run button (PlayArrow icon). Click Run; the right side shows success or error indicator, output log, and the return value (JSON-formatted).
The Db helper, see the full reference in Script-callable APIs below.
Calling from a Business Event: add an Execute Script action whose body calls the module:
await Modules.CallAsync("RecalcCompanyOpenDealValue", new Dictionary<string, object?> {
["company_id"] = Entity.company_id
}); Calling from a Scheduled Event: create an event with the
On Schedule trigger, set the cron, add an Execute Script action with the
same Modules.CallAsync body.
Logging: every invocation writes an entry to Event Logs with execution time, parameters, and result (or error).
Gotchas: always await Db calls, forgetting compiles but
returns a Task. Returns are object-passed (not JSON-serialised by the framework); for
front-end consumption, return anonymous objects with primitive fields. Suggestion-on-commit-character
is intentionally disabled in the editor; use Tab to accept.
Frontend Templates
Reusable UI fragments written in TSX that the page editor can drop into a page. Useful when configured templates aren't enough, bespoke charts, unusual layouts, third-party widget integrations.
Common workflow: click Create, name the template, write a TSX
component in the editor. The component receives props: record (the current row
when embedded in a detail form), refresh (re-fetch data), and a few helpers.
Save. Reference the template from a Page Editor field.
Packageable: templates flow through the Package system like any other config object, they're cascaded in when a page that uses them is added to a package.
Gotchas: the TSX is sandboxed, you can't import arbitrary npm packages. Stick to React + the supplied helpers.
PDF Templates
HTML + Scriban template + a small C# data script that renders to a PDF (headless Chromium). Each template is invokable by name from a script via REST or directly from a button on a Page (with a small adapter, see below).
Common workflow: click + Create. The editor opens with Name and Description text fields at the top, five tabs on the left side (HTML Template, Data Script, Settings, Params, JSON), and an always-on PDF Preview pane on the right. The Data Script and HTML Template tabs each open a Monaco editor on the left half; the preview re-renders when you save (green Update button top-right).
Declare parameters in the Params tab via the + Add button (Name, Type, Required, Default Value). The tab label updates to Params (n) with the count. Page format / orientation / margins / scale / header / footer live in the Settings tab.
Data script shape (see PDF Invoices tutorial for a real one):
var deal = await Db.GetAsync("deal", deal_id);
var lines = await Db.From("deal_line")
.Where("deal_id", "=", deal_id).ToListAsync();
return new {
InvoiceNumber = $"INV-{deal.id:D6}",
Lines = lines.Select(l => new { l.description, l.quantity })
}; HTML template shape (Scriban, similar to Liquid/Handlebars):
<h1>{{ InvoiceNumber }}</h1>
<table>
{{ for line in Lines }}
<tr><td>{{ line.description }}</td><td>{{ line.quantity }}</td></tr>
{{ end }}
</table> Preview pane: always visible on the right side of the editor. Re-renders when you save (green Update button). Includes a thumbnail column for multi-page PDFs and a built-in viewer toolbar (zoom, rotate, download, print). The icon buttons in the editor's top-right toggle the preview pane's visibility and full-screen editing.
Calling it from outside the editor:
POST /api/v1/pdf-templates/{id}/preview- renders by ID, returnsapplication/pdf. Used by the editor's preview pane.POST /api/v1/pdf-templates/generate/{name}- renders by name, returnsapplication/pdf.POST /api/v1/pdf-templates/generate/{name}/base64- same but returns{ "data": "<base64>" }.
Pass parameter values in the JSON body. Auth is the standard Bearer token.
Trial limitation: both the generate endpoint and the
{id}/preview endpoint (used by the editor's preview pane) return 403
in trial mode, PDF rendering is gated entirely. You can still author the template
(data script, HTML, settings, parameters) and save it; you just won't see a rendered PDF
until you're on a paid environment.
Note on script-callable rendering: the C# scripting context does not
expose a Pdf global. To render a PDF from a Script Module today, call the
generate REST endpoint via HttpClient. (The script editor's
autocomplete advertises Pdf.RenderAsync for forward compatibility; the runtime
binding isn't wired yet.)
Gotchas: Scriban escapes HTML by default. Page breaks are CSS-driven,
page-break-before: always on a section. Fonts available on the rendering
server are limited to system fonts plus the Noto and Liberation families, embed your own
via base64 @font-face if you need a brand font.
Scheduled Events
The same Event Trigger system as Business Events, with the On Schedule trigger ticked. Use it for nightly recalculations, weekly digests, periodic cleanups, monthly reports.
Common workflow: create a Business Event with the On Schedule
trigger (instead of, or in addition to, the data-change triggers). Configure the cron
expression. Add an Execute Script action whose body invokes a Script Module via
Modules.CallAsync(...).
Cron expressions: Quartz-style 6 or 7 field, see Concepts → Scheduled Events for common patterns. Times are server time (UTC on the production stack).
Gotchas: a long-running job that overruns its cron interval doesn't double up, Quartz won't fire a second instance of the same job concurrently, the second tick is skipped. Failed runs don't auto-retry; build retry into the script.
Packages
Exportable bundles of configuration objects (BEs, pages, events, scripts, templates, schema) with cascading dependencies. Adding a Page to a package automatically pulls in everything that page references.
Common workflow (export): click Create, name and version the package. Add the top-level objects you care about, usually a handful of pages. Open the linked-items panel to see the cascaded list (what the package will actually contain). Untick anything you want to omit. Decide whether to include data (toggle on export). Click Export, you get a ZIP.
Common workflow (import): on the destination environment, open Packages, click Import, upload the ZIP. The platform shows what will be added or updated. Schema deltas are not auto-applied, if the package references a column that doesn't exist on the destination, import fails.
Gotchas: identifiers are by name, not numeric ID. A BE called "customer" on source binds to a BE called "customer" on destination, rename either side and the link breaks.
Third-Party Data Connections
Connections to external databases (Postgres, SQL Server, MySQL, others) that import tables on a schedule. Imported tables behave like native tables, they show up in Schema Designer and can be referenced by Business Entities, Pages, and Scripts.
Common workflow: create a connection (connection string, test, save). List available remote tables; pick which ones to import. For each imported table, set sync mode (Full / Delta) and sync trigger (cron / manual / fixed interval).
Mirrored, not federated: the imported data lives in your own
omnicore-db. Reads are fast (local Postgres). Trade-off: staleness between
syncs.
App-managed columns: mark some columns as "managed by Archestack", they won't be overwritten by future syncs. Useful when you want to annotate vendor data with your own status flags.
Gotchas: the first sync of a large table can take a while. Delta syncs
require a column the connector can use as a watermark, usually a modified_at
timestamp.
AI Builder
A guided wizard where you describe a feature in natural language and the wizard generates a build plan, schema additions, BE changes, pages, events, that you review line-by-line and apply. The plan generation is done by an external LLM of your choice (Claude, ChatGPT, Gemini), the wizard composes a self-contained prompt for you to paste in and accepts the JSON response back. There is no in-app chat assistant anymore.
Common workflow: describe the feature in 1-3 sentences ("add support for service appointments, each one has a date, a customer, a vehicle, and a status"). Click Generate prompt, paste it into your external LLM, paste the JSON response back into the Import step, click Review. Untick anything you don't want. Click Apply.
Works well for: CRUD-shaped features, simple automations, straightforward extensions to existing entities.
Less well for: non-obvious business rules, complex multi-step workflows. The plan is a starting point, review it like a junior dev's PR.
Gotchas: apply is irreversible. For risky features, take a backup first (or apply on a trial environment, evaluate, then rebuild on production from the resulting Package).
Object Browser
Raw view of every native and imported table, add, edit, delete rows directly. Includes a CSV import for bulk loading.
When to reach for it:
- Bulk-loading reference data before pages exist (countries, currencies, status enums).
- Debugging, eyeballing what's actually in a table when a page is misbehaving.
- Quick admin edits to data that's not yet exposed via a Page.
- CSV import: pick a table, upload a file, map columns to fields, preview, commit.
Gotchas: Object Browser does fire Business Events (it goes through the same EntityService write path as the front end). The exception is direct SQL via Database Deployments, that bypasses everything.
Dependency Graph
A read-only map of how the configuration objects in your environment reference each other. Eight resource types appear as nodes, coloured by type: Business Entities, Pages, Event Triggers, Script Modules, PDF Templates, Frontend Templates, Scheduled Events, and Packages. An edge between two nodes means one resource depends on the other. Reach for it to answer "what would break if I delete this?" and "is anything here unused?".
What an edge means: a Page bound to a Business Entity, an Event Trigger
registered on a Business Entity, a Page action step that fires an Event Trigger, a script
that calls another Script Module via Modules.CallAsync("Name"), a script that
renders a PDF Template, a Scheduled Event that fires an Event Trigger, or a Package that
contains a resource. It is a design-time view, it reflects what your configuration
wires together, not live runtime traffic.
Two kinds of problem are made to stand out visually:
- Loose ends (orphans) - nodes that nothing references and that reference nothing. They are collected into a red-bordered "Loose ends" band at the bottom of the canvas. A Script Module no one calls; a Page bound to nothing.
- Dangling references - drawn as red dashed edges. The reference exists in configuration but its target cannot be found, for example a Page action step still pointing at an Event Trigger that was deleted, or a script that calls a Script Module by a name that no longer exists.
Common workflow: open Dependency Graph from the Tools section of the sidebar. The left panel has a name search, a Layout picker (Left to right, Top to bottom, By type, Force-directed), the Group by package, Show loose ends and Show edges toggles, and a per-type checklist. Click any node to enter focus mode: that node and its direct neighbours stay bright while the rest of the graph dims, so you can trace exactly what one resource touches. Click the canvas background to clear focus. Every node carries a small open-in-new-tab button that jumps straight to that resource's editor. The stats line at the top reports node, edge, orphan, and dangling-reference counts.
Group by package: wraps each package's members in a labelled dashed box, so you can see a package as a unit and spot the resources it pulled in by cascade.
Gotchas: the graph is built on demand from the current state, use the
refresh button after you change resources elsewhere. Script-derived edges are found by
scanning script bodies for the literal Modules.CallAsync("Name") call shape, a
module invoked through a computed name will not show an edge. A dangling reference is always
worth fixing; an orphan often is not, a brand-new Script Module counts as orphaned until
something calls it.
Event Logs
The audit trail for everything the platform ran, Business Events, Script Modules, Scheduled Events, third-party syncs.
Common workflow: open Event Logs, set Status to Failed, click into an entry. The detail panel shows the payload (record state at the time, parameters passed), the exception with stack trace, and timing.
Logs and Charts: the page has two tabs sharing one filter row. Logs is the table above. Charts is an operations dashboard over the same filter: volume over time stacked by status, a success-rate trend, a per-source breakdown, the top failing and slowest events, average and max duration over time, a trigger-type breakdown, and a day-of-week by hour-of-day heatmap, with headline totals on top. Both tabs share a From / To date range (with Last 1h / 24h / 7d presets); the Charts tab auto-picks a sensible time bucket for the range and can refresh on an interval for live monitoring.
Real-time notifications: the bell icon in the top bar of the admin app surfaces unread failures via SignalR.
Gotchas: the log can grow large. Old entries are kept forever by default; if disk is a concern, set up a Scheduled Event that prunes entries older than N days.
User Management
Create, invite, and manage users; assign realm roles. Wraps the Keycloak admin REST API.
Roles:
- admin - everything; can manage all Business Units, users, roles, system settings.
- owner - manages their own Business Units, can edit administration and runtime pages.
- editor - can view and edit runtime pages, but not change schema/BE/event configuration.
- user - read-only on runtime pages.
Gotchas: demoting a user from admin mid-session doesn't kick them out, the change applies on next token refresh (typically within a minute).
Business Units
Keycloak-backed groups that gate visibility on individual resources. See Concepts → Business Units for the model.
Common workflow: create a BU. Invite members by email and pick the role. Assign resources by going to each BE / Page / Trigger or via the BU's linked-items panel.
Cascading assignments: when you assign a Page to a BU, the assignment cascades to the BE the page binds to.
Gotchas: a user's active BU is set in the top-bar BU switcher and
persists in localStorage. New users default to their first BU; if a list page
is suspiciously empty, check which BU is active.
Database Backups
Nightly pg_dump of all three databases. Manual trigger / upload / restore from
the UI.
Common workflow: Trigger now → Download → Upload → Restore. Schedule defaults to nightly at 03:00 UTC.
Trial limitation: Backups are disabled in the trial environment.
Branding
Change the app's display name, logo (light + dark), and primary colour scheme, applied at runtime, no rebuild.
Common workflow: set app name, upload a light logo and a dark logo (PNG/JPG/SVG, ≤ 10 MB), pick a primary brand colour. Save. Refresh the browser.
Gotchas: the favicon is not overridden by Branding, it's bundled in the SPA.
Translations
Key/value editor for the app's UI strings. Add new locales, override default labels per customer.
Common workflow: pick a locale, search for a key, type a new value, save. Refresh the admin app.
Gotchas: translations are loaded once per session; users on a stale tab don't see changes until they refresh. Some labels (column headers from BEs) come from the BE config, not from translations.
Script-callable APIs
The exact surface scripts can call. Verified against the source, every method below actually exists.
Globals
Every script gets these top-level variables (no other names exist in script scope):
Entity-dynamicExpandoObject. Current record. Access fields withEntity.column_name. MutatingEntityin a Before* trigger persists the changes as part of the in-flight write.OldEntity-dynamicExpandoObject. Previous record (update + delete triggers). Read-only.Log-ILogger.Log.LogInformation("…"),Log.LogWarning(…),Log.LogError(…). Writes to server logs and the Event Log entry.Db-DbHelper. Database access (see below).Modules-ModulesHelper. Call other Script Modules:await Modules.CallAsync("ModuleName", new Dictionary<string, object?> { ["param"] = value }). ReturnsTask<object?>.Pdf-ScriptPdfHelper. Render a stored PDF template by name:await Pdf.GenerateAsync("invoice", new Dictionary<string, string> { ["id"] = Entity.id.ToString() })returnsbyte[];await Pdf.GenerateBase64Async(…)returns the same payload base64-encoded. Parameters are forwarded to the template's data script as string-typed locals.
There is no User, Http, Email,
Templates, or any other global.
Db - single-row + write methods
All async, all on the top-level Db object:
await Db.GetAsync(string table, object id)- get a row by primary key. Returnsdynamic?(ornullif not found).await Db.CreateAsync(string table, object values)- insert.valuescan be an anonymous object or aDictionary<string, object?>. Fires Before/After Create triggers. Returnsint(the new ID).await Db.UpdateAsync(string table, object id, object values)- update by PK. Fires Before/After Update triggers.await Db.DeleteAsync(string table, object id)- delete by PK. Fires Before Delete trigger.
Db - fluent query builder
Start with Db.From(table). Chain filters, then a terminal:
.Where(string column, string op, object? value = null)- add a WHERE clause. Operators:=,!=/<>,>,>=,<,<=,LIKE,ILIKE,IN,IS NULL,IS NOT NULL. Chain multiple.Where(...)calls for AND..OrderBy(string column, bool desc = false)- set ORDER BY. Only one allowed per query..Limit(int limit)- max rows. Default is 1000.
Terminals (each runs the query):
await ...ToListAsync()- returnsList<dynamic>.await ...FirstAsync()- returnsdynamic?(first match or null). Temporarily sets limit to 1.await ...CountAsync()- returnsint.
There is no SumAsync, MaxAsync,
FirstOrDefaultAsync, or SingleAsync. For aggregations, fetch with
ToListAsync() and reduce in C#.
Worked example
// Get a row by ID, query a list, calculate a sum, update.
var order = await Db.GetAsync("orders", 42);
if (order == null) return false;
var items = await Db.From("order_items")
.Where("order_id", "=", order.id)
.OrderBy("id")
.ToListAsync();
decimal totalAmount = 0;
foreach (var item in items) {
totalAmount += (decimal)(item.amount ?? 0);
}
await Db.UpdateAsync("orders", 42, new {
total = totalAmount,
updated_at = DateTime.UtcNow
});
return true; Simulation mode
During trigger simulation (the Run Simulation button on a Business Event), the
platform sets Db.SimulationMode = true. All write operations
(CreateAsync, UpdateAsync, DeleteAsync) run inside a
PostgreSQL transaction that's always rolled back at the end. Read operations work normally.
This is what makes Run Simulation safe to use against real data.
Modules
await Modules.CallAsync(string moduleName, Dictionary<string, object?>? parameters = null)- call another Script Module by name. Parameters become typed variables in the called module's script. ReturnsTask<object?>(whatever the module returned).
Template expressions (Business Event actions)
Action configuration uses {{ … }} braces. The full list of valid tokens:
{{ Entity.column_name }}/{{ Entity.id }}{{ OldEntity.column_name }}{{ now() }}/{{ getdate() }}- ISO 8601 UTC datetime{{ today() }}- date string{{ guid() }}/{{ newid() }}- fresh GUID{{ year() }},{{ month() }},{{ day() }},{{ timestamp() }}- Unix seconds{{ empty() }},{{ null() }}- Aggregates within a join context:
{{ SUM(column) }},{{ AVG() }},{{ COUNT() }},{{ MIN() }},{{ MAX() }} - Arithmetic:
{{ Entity.quantity * Entity.price }}- evaluated post-substitution viaDataTable.Compute()
There is no {{ user.email }} or other user-scoped token.