ObjectStackObjectStack

Context Tokens

Context Tokens protocol schemas

Context Tokens — the declarative placeholders that resolve against the caller's session (who am I, which org am I in) rather than the clock.

Why this lives in spec

These are the sibling vocabulary to {date-macros}. Filter values in dashboards, views, reports and pages travel as JSON, so a user-scoped slice cannot call currentUser().id inline — it writes a placeholder:

{ owner_id: '{current_user_id}' }
[{ field: 'owner', operator: 'equals', value: '{current_user_id}' }]

Like date macros, the placeholders are expanded on both sides of the wire (framework#3582): resolveContextTokens() in @object-ui/core before the filter leaves the browser, and resolveFilterTokens() in @objectstack/core on the ObjectQL read AND write paths and the analytics dataset executor, for filters that reach the database without passing through a renderer. The DRIVER only ever sees concrete ids, never {tokens}.

The write verbs matter as much as the read ones (#3810): a filter has to select the same rows whether find, update or delete consumes it, or a flow that previews with one and acts with the other operates on two different row sets.

The server resolver reads ExecutionContext{current_user_id} is userId, {current_org_id} is tenantId. A request that carries neither is an ERROR, not a null comparand: resolving to null degrades to IS NULL on most drivers and would hand back the rows the filter was written to exclude.

Presentation scope, NOT a security boundary

This is the single most important thing to understand about these tokens. {current_user_id} scopes what a surface shows; it does not decide what a caller is allowed to read. Enforcement is RLS, which uses a different and genuinely server-side vocabulary rooted at current_user (owner_id = current_user.id, compiled by @objectstack/plugin-security's RLS compiler).

The two look alike and are easy to confuse, so keep them straight:

{current_user_id}current_user.id
Wherefilter values (JSON)RLS using expressions
Resolvedclient-side, before the queryserver-side, during the query
Purposepresentation scopeaccess enforcement
Bypassableyes — it's just a filterno

Never reach for a context token to keep a user away from data. Removing a {current_user_id} filter widens a view; it must never widen access.

Where the tokens are honoured

Filter values on every surface that resolves placeholders — object list views, dashboard widgets, reports, SDUI page components. Navigation (recordId / params) additionally resolves AppContextSelector ids such as {active_package}; those are nav-only and are NOT valid inside filter values, because filters are not evaluated with the sidebar's selector state.

Out of scope

  • current_user.* RLS expressions — see @objectstack/plugin-security.
  • {date-macros} — the clock-based sibling; see ./date-macros.zod.ts.
  • titleFormat field interpolation ({user_id} etc.) — that substitutes record fields, an unrelated mechanism that happens to share braces.

Source: packages/spec/src/data/context-tokens.zod.ts

TypeScript Usage

import { ContextTokenSchema, ContextTokenPlaceholderSchema } from '@objectstack/spec/data';
import type { ContextToken, ContextTokenPlaceholder } from '@objectstack/spec/data';

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

ContextToken

Type: string


ContextTokenPlaceholder

Type: string


On this page