ObjectStackObjectStack

Date Macros schema — Data Protocol reference

Date Macro Tokens — the declarative placeholders the UI substitutes into filter values before sending a query to the data engine.

Date Macro Tokens — the declarative placeholders the UI substitutes into filter values before sending a query to the data engine.

Why this lives in spec

Filter values in dashboards, views, reports and pages travel as JSON. Because JSON cannot evaluate code, callers cannot write daysAgo(30) inline; they use a tiny placeholder grammar instead:

{ published_at: { $gte: '{last_quarter_start}' } }
{ signal_at:    { $gte: '{30_days_ago}' } }

The placeholders are expanded on both sides of the wire, so a filter behaves the same wherever it is executed (framework#3582):

  • Client — resolveDateMacros() in @object-ui/core, just before the filter is handed to the data source.
  • Server — resolveFilterTokens() in @objectstack/core, wired into the ObjectQL read AND write paths (find/findOne/count/ aggregate/update/delete) and the analytics dataset executor. Filters that reach the database WITHOUT passing through a renderer — dashboard widgets, dataset definitions, REST query params, flow node filters — need this: before it, the token compared as a literal string and matched nothing. The write verbs are covered for the same reason (#3810): one filter must select one row set regardless of which verb consumes it, or a flow's find preview and its update act on different rows.

Either way the DRIVER only ever sees ISO date / timestamp strings, never {tokens}. Translating an ISO comparand into a column's on-disk form — canonical UTC text on SQLite, a native timestamptz on Postgres, a DATETIME(3) literal on MySQL, and YYYY-MM-DD text for a calendar day on every dialect — is the driver's job; see SqlDriver.temporalFilterValue.

A token OUTSIDE this vocabulary is rejected rather than passed through: @objectstack/lint's validate-filter-tokens fails the build, and the runtime resolver throws. Silently matching nothing is the failure mode the vocabulary exists to prevent.

AI agents and template authors author these placeholders directly, so the set of recognised tokens is part of the platform contract and must live here next to the rest of the JSON-DSL schemas, not inside any single UI implementation.

Two flavours of token

  1. Fixed tokens — small, finite list ({today}, {current_quarter_start}, {last_year_end}, …). Enumerated by DATE_MACRO_TOKENS below.

  2. Parameterised tokens — {N_days_ago}, {N_weeks_from_now}, etc., where N is any non-negative integer. Matched by DATE_MACRO_PARAM_RE. Units: minute(s), hour(s), day(s), week(s), month(s), year(s). Directions: ago, from_now.

Out of scope

  • CEL expressions (cel\daysAgo(30)`) run **server-side** in the formula engine. They are unrelated to these placeholders; see @objectstack/formula`.
  • Token resolution semantics (week-start day, timezone, fiscal calendars) are defined by the resolver implementation; spec only freezes the vocabulary. One property is worth stating here because it is authored against: a *_end token is the period's last calendar DAY ({current_year_end} → 2026-12-31). What a bare day DENOTES once it lands on one side of an operator is a separate contract, stated once in packages/spec/src/data/calendar-day.ts (ADR-0053 D-D) and deliberately not restated here: as an upper bound ($lte, a $between max, a dateRange end) a bare day means the WHOLE day, compiled half-open to the next calendar day, so <= {current_year_end} reaches the final instant of Dec 31; as $gte / $gt / $lt it means that day's 00:00:00.000. ⛔ Do NOT hand-write a half-open detour (< {next_year_start}) to cover a period's last day on a datetime column: every backend already applies the widening, and packages/spec/src/data/temporal-conformance.ts pins it cross-driver.

Source: packages/spec/src/data/date-macros.zod.ts

TypeScript Usage

import { DateMacroPlaceholderSchema, DateMacroTokenSchema } from '@objectstack/spec/data';
import type { DateMacroPlaceholder, DateMacroToken } from '@objectstack/spec/data';

// Validate data
const result = DateMacroPlaceholderSchema.parse(data);

DateMacroPlaceholder

Type: string


DateMacroToken

Type: string


On this page