Portal Community

Root Template Fields

The root object is the entry point of every template. It is identified by "isTemplate": true and is the only record directly addressable by the API via its catalogue code.

Field Type Required Default Description
name string Yes Human-readable name for the template. Used in admin UIs and diagnostic logs. Does not affect execution.
sql string Yes The SQL SELECT statement executed for the root level. Must filter by TenantID = @TenantID. Supports @ParameterName tokens declared in the parameters array.
recordType string Yes A label identifying the type of record returned (e.g., "Department", "PayrollRecord"). Appears in HTML output headers and diagnostic traces.
isTemplate boolean Yes Must be true on the root object. Marks this record as the API-addressable entry point. All child and grandchild records must set this to false.
parameters array No [] Caller-supplied parameters (excluding TenantID, which is injected automatically). Each entry is a Parameter Object — see below.
options object No See Options Controls output format, caching, and recursion depth. See the Options Object table below.
children array No [] Zero or more ChildCollection objects defining the next level of the hierarchy. Each child is executed once per root row, with @parent tokens resolved to the current root row's values.

Parameter Object

Each entry in the parameters array declares one caller-supplied SQL parameter. Parameters are resolved from the API query string, the POST body, or the EvaluationContext (for expression directives).

Field Type Description
name string Parameter name without the @ prefix (e.g., "DepartmentID"). Must match the token used in the SQL string.
type string SQL type for the parameter. Accepted values: int, nvarchar, bit, datetime. The engine uses this to cast the incoming string value before binding it to the SQL command.
defaultValue any / null Value used when the parameter is not supplied by the caller. Set to null for required parameters — the engine will return an error if a null-default parameter is missing at runtime.

Options Object

The options object controls how the engine formats and caches results. All fields are optional; the defaults shown below are applied when the field is absent.

Field Type Default Description
outputFormat json | html json Controls the response format. json returns a compact JsonArray tree. html returns an expandable cascaded HTML table. Can be overridden at call time via the endpoint suffix (.json / .html) or via pipe options in the expression directive.
outputFormatTemplate fragment | standalone fragment Applies when outputFormat is html. fragment returns a bare table suitable for embedding in an existing page. standalone wraps the table in a complete <html> document with inline styles — suitable for email bodies or saved files.
maxDepth int 5 Maximum recursion depth the engine will traverse. Prevents runaway queries on self-referencing hierarchies. Set lower (e.g., 3) for shallow trees to improve clarity.
cacheSeconds int 0 Duration in seconds to cache the result for identical parameter combinations. 0 disables caching (always fresh). Use 60300 for read-heavy, infrequently-changing data. Always use 0 for audit and compliance queries.

ChildCollection Object

Each entry in the children array defines one nested collection. The engine executes the child SQL once for every row returned by the parent query, substituting @parent.* tokens with the current parent row's column values.

Field Type Description
collectionName string The key used in the JSON output (or the section label in HTML output) for this child array. Use camelCase (e.g., "employees", "auditEvents").
sql string The SQL SELECT statement for this child level. Must reference @parent.ColumnName to join to the parent row and must filter by TenantID = @TenantID.
recordType string Label for records at this level. Used in HTML headers and diagnostic output.
isTemplate boolean Always false for children. Child records are not addressable directly via the API — they are always fetched as part of a root template execution.
children array Recursive. Zero or more ChildCollection objects defining the next level beneath this one. Nesting is limited by maxDepth in the root options.

SQL Writing Guidelines

Never Use SELECT * in Production

Always list columns explicitly in production templates. SELECT * breaks in two ways: (1) schema changes — a new column added to the underlying table is silently returned to all callers, potentially exposing sensitive data; (2) performance — unnecessary columns are transmitted, serialized, and cached, increasing latency and storage cost. List only the columns your consumers actually need.

Full Example — 3-Level Template

A realistic three-level template: Clients (root) → Projects (child) → Timesheets (grandchild). Each level uses explicit column lists, TenantID filtering, and @parent token joins.

{
  "name": "Client Project Timesheets",
  "sql": "SELECT c.ClientID,
                 c.ClientName,
                 c.IndustryCode,
                 c.AccountManagerID,
                 c.ContractStartDate,
                 c.ContractEndDate
          FROM   Clients c
          WHERE  c.TenantID  = @TenantID
            AND  c.IsActive  = 1
            AND  c.Deleted   = 0
            AND  c.Archived  = 0
          ORDER BY c.ClientName",
  "recordType": "Client",
  "isTemplate": true,
  "parameters": [
    { "name": "PeriodStart", "type": "datetime", "defaultValue": null },
    { "name": "PeriodEnd",   "type": "datetime", "defaultValue": null }
  ],
  "options": {
    "outputFormat": "json",
    "outputFormatTemplate": "fragment",
    "maxDepth": 5,
    "cacheSeconds": 120
  },
  "children": [
    {
      "collectionName": "projects",
      "sql": "SELECT p.ProjectID,
                     p.ClientID,
                     p.ProjectName,
                     p.ProjectCode,
                     p.Status,
                     p.BudgetHours,
                     p.BillableRate
              FROM   Projects p
              WHERE  p.ClientID  = @parent.ClientID
                AND  p.TenantID  = @TenantID
                AND  p.Deleted   = 0
              ORDER BY p.ProjectName",
      "recordType": "Project",
      "isTemplate": false,
      "children": [
        {
          "collectionName": "timesheets",
          "sql": "SELECT ts.TimesheetID,
                         ts.ProjectID,
                         ts.EmployeeID,
                         ts.WorkDate,
                         ts.HoursLogged,
                         ts.IsBillable,
                         ts.NarrativeNote,
                         ts.ApprovedBy,
                         ts.ApprovedOn
                  FROM   Timesheets ts
                  WHERE  ts.ProjectID  = @parent.ProjectID
                    AND  ts.TenantID   = @TenantID
                    AND  ts.WorkDate  >= @PeriodStart
                    AND  ts.WorkDate  <= @PeriodEnd
                    AND  ts.Deleted   = 0
                  ORDER BY ts.WorkDate DESC",
          "recordType": "Timesheet",
          "isTemplate": false,
          "children": []
        }
      ]
    }
  ]
}

Store this with catalogue code MQ_CLIENT_PROJECT_TIMESHEETS and call it as:

GET /api/v1/expressions/multiquery/MQ_CLIENT_PROJECT_TIMESHEETS.json
    ?PeriodStart=2025-11-01&PeriodEnd=2025-11-30
Authorization: Bearer <token>