Skip to content
ahmamed
WritingAbout
EN·FR·DE
Also in:Français·Deutsch

Dynamic values and template directives in APEX 26.1 REST Data Sources

May 25, 202611 min read
All writing ←

Abdelmounaim Hmamed

hi@ahmamed.dev

REST Data Sources are how APEX talks to the outside world declaratively. Anything you would otherwise write thirty lines of PL/SQL for — building a URL, signing it, posting a body, parsing the response — a REST Data Source lets you describe instead, in a few panels in the Builder. Once defined, every report, form, and process in the application can read from it the same way they read from a local table.

APEX 26.1 ships five enhancements to REST Data Sources. The release notes cover them all (OAuth client credentials grant improvements, expanded security header support, two operations-editor refinements), but this post focuses on the two that touch every developer who builds against external APIs: dynamic values for REST source parameters, and template directive support inside Request Body templates.

These two changes share a theme. Both replace patterns that previously required leaving the declarative panel — pre-sync PL/SQL processes, or post-render payload manipulation — and move that work back into the Builder. The result is fewer pieces to maintain and a smaller surface area where things can quietly drift out of sync.

A note on framing: the views below are the author's own, not Oracle's, and the post is a documentation walkthrough rather than insider reporting.

This post is in two parts: Dynamic values for REST source parameters and Template directives in request body.

Dynamic values for REST source parameters

The old constraint

Before 26.1, the Default Value field on a REST Data Source parameter accepted one thing: a static string. You could type a literal, or you could include substitution strings like &APP_USER. and &P10_SEARCH_TERM. — but the underlying value was still a string evaluated once, with no programmatic logic behind it.

That worked for parameters that were genuinely constant. It started to fall apart the moment a parameter needed to come from a query, a session computation, or a function call.

Two workarounds dominated. The first was to add a Before Header (or Before Region) process on the page hosting the report, write the dynamic value into a page item via PL/SQL, and then reference that page item in the parameter default with a substitution string. The second was to give up on the declarative source entirely and rebuild the call by hand with apex_web_service.make_rest_request, parsing the response into a collection or PL/SQL type and returning it through a function-based report.

Both work. Both also duplicate logic that already exists on the REST source — the URL, the headers, the auth, the response mapping — and split it across two or three places that have to stay in lockstep. The Oracle Forums have years of threads asking variations of the same question: "how do I default a REST parameter from a query without using a page process?" The answer was always the same workaround.

What shipped in 26.1

In 26.1, the Default Value field becomes a type-aware picker. The release notes describe five options for each parameter default:

  • Static — plain text, the original behavior.
  • Page Item — the value of an APEX page item at the moment the REST operation is invoked.
  • SQL Query — a single-row, single-column SELECT.
  • Expression — a short PL/SQL or JavaScript expression.
  • Function Body — a multi-statement PL/SQL or JavaScript block returning a value.

The picker sits inline with the parameter row in the operation editor, so each parameter chooses its own default type. The type determines what editor opens for the value — a single-line input for Static and Page Item, a SQL editor for SQL Query, a multi-line PL/SQL or JavaScript editor for Expression and Function Body.

One example per option type

Static. The original behavior, kept for the cases that never needed anything else. A literal API key in a header parameter:

sk_live_3f7c9a2b1d4e8f6a

Useful when the value is genuinely constant, or when an APEX substitution string (&APP_ID., &APP_USER.) does the job.

Page Item. A search parameter on a /products GET that takes its value from the search field on the same page:

P10_SEARCH_TERM

This is the most common case the old workaround was solving. Now it is a single line, no page process required.

SQL Query. An updated_since parameter on an incremental sync, reading the most recent successful sync timestamp from a local log table:

select to_char(max(synced_at), 'YYYY-MM-DD"T"HH24:MI:SS"Z"')
from   product_sync_log
where  status = 'OK'

Use this when the value lives in the database and a single query can produce it. The query returns null on an empty result; the receiving API has to tolerate that or the query needs a sensible fallback.

Expression (PL/SQL). A since parameter computed as one hour before the current timestamp:

to_char(systimestamp - interval '1' hour, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')

Expressions are right when the logic fits in one line and does not need a begin / end block. Keeping them small keeps the parameter table readable.

Function Body (PL/SQL). A tenant-scoped filter that reads the current tenant from session context and falls back to a default when none is set:

declare
  l_tenant varchar2(64);
begin
  l_tenant := sys_context('apex$session', 'tenant_id');
  if l_tenant is null then
    l_tenant := 'public';
  end if;
  return l_tenant;
end;

Function Body is the right tool when the logic needs multiple statements, exception handling, or a temporary variable. Anything more than four or five lines probably belongs in a packaged function the body just calls.

A worked use case: incremental sync without a pre-process

A common pattern before 26.1, for an interactive report backed by a REST source that supports ?updated_since=:

  1. Create a hidden page item P20_LAST_SYNCED_AT.
  2. Add a Before Header process that queries max(synced_at) from the local log table and assigns it to the item.
  3. In the REST source operation, set the updated_since parameter default to &P20_LAST_SYNCED_AT..

Three artefacts to maintain. The page item exists only to bridge the gap between the process and the parameter. If anyone deletes the process by mistake, the report silently re-syncs from the beginning of time.

After 26.1:

  1. Set the updated_since parameter default to SQL Query, with the timestamp select shown above.

One artefact. The page item and the process both disappear. The query lives next to the parameter it feeds, which means the next developer reading the source sees the data flow without having to hunt across page processes.

Gotchas worth knowing

  • Defaults are resolved at REST invocation time, not at page render. A SQL Query default re-runs each time the operation is called — the desired behavior for incremental sync, but worth knowing if the query is expensive and the operation is called from inside a loop.
  • A SQL Query that returns zero rows produces a null parameter value. APEX passes that to the operation as-is; the receiving API has to either accept null, or the query needs a fallback (nvl, coalesce, or a union all with a sentinel row).
  • PL/SQL Expression versus Function Body is the same trade-off as everywhere else in APEX: Expression for one-liners, Function Body when control flow, declarations, or exception handling enter the picture.
  • The Static option still expands APEX substitution strings (&APP_USER., &P10_FOO.). Substitution strings and dynamic values are not the same mechanism — substitution is text replacement on the template, dynamic values are computed at runtime. Pick whichever describes intent better; for anything beyond a page item, dynamic values are easier to read.

Template directives in request body

The old constraint

Request Body Templates have always supported #PARAM# placeholder substitution. The substitution is naïve — wherever #FIRSTNAME# appears in the template, APEX replaces it with the value of the FIRSTNAME parameter, no questions asked. There was no way to:

  • Conditionally include a property only when its value was non-null
  • Loop over a collection to build a JSON array from delimited input
  • Apply a specific escape strategy (URL encoding, JSON escaping) to one substitution without doing it to all of them

A small example pinpoints the problem. Suppose the body template is:

firstname=#FIRSTNAME#&age=#AGE#

When AGE is null, the template renders as firstname=Alice&age= — or worse, as firstname=Alice&age=null if the parameter was filled with the literal string null. Most APIs treat age= differently from "no age key at all", and some reject the request outright.

The community workaround has been to render the body in PL/SQL using apex_application.do_substitutions (or a hand-rolled equivalent) and then patch out the empty fragments before the request goes out. The Oracle Forums have a thread from Tadas Blinda in late 2024 asking exactly this — "how do I conditionally exclude a parameter from a request body" — and the accepted answer is, essentially, post-processing in PL/SQL. The whole point of using a Request Body Template was to avoid building the body in PL/SQL.

What shipped in 26.1

APEX 26.1 introduces Template Directive support inside Request Body Templates. The directives are the same ones that power Template Components and theme templates across the rest of the product; the new behavior is making them available inside the body of a REST operation.

The directives developers will reach for most often:

  • {if VALUE/} ... {endif/} — include the fragment only when VALUE is set
  • {case VALUE/} ... {when 'foo'/} ... {endcase/} — branch on a value
  • {loop "delim"/} ... {endloop/} — iterate over a delimited string
  • {with/} ... {apply TEMPLATE/} — bind variables and reuse a named partial

Alongside directives, the existing escape modifiers on &PARAM. substitutions also apply inside the body:

  • &PARAM!RAW. — no escaping, the raw value
  • &PARAM!HTML. — HTML escaping
  • &PARAM!JSON. — JSON-escape the value, including quotes and control characters
  • &PARAM!URL. — URL-encode the value for query strings and form bodies

The escape modifier choice is the same idea as htmlspecialchars in PHP or JSON.stringify in JavaScript: the body template is treated as data, not code, and each substitution is escaped for its target context.

Three patterns to know

Pattern 1 — Conditional property inclusion (Tadas Blinda's example, solved).

A user-update endpoint that takes a JSON body with required and optional fields. firstname and email are always present; age is optional. The 26.1 template:

{
  "firstname": "&FIRSTNAME!JSON.",
  {if AGE/}"age": &AGE.,{endif/}
  "email": "&EMAIL!JSON."
}

When AGE = 34, the body renders as:

{
  "firstname": "Alice",
  "age": 34,
  "email": "alice@example.com"
}

When AGE is null, the {if/} fragment is dropped entirely:

{
  "firstname": "Alice",
  "email": "alice@example.com"
}

The receiving API now sees a body where age is absent rather than null, which is what most REST conventions expect for optional fields.

Pattern 2 — Looping over a collection to build an array.

A tagging endpoint where the application has a comma-delimited list of tags in a page item (P30_TAGS = "red,green,blue") and the API wants a JSON array. The template uses {loop/} with a comma delimiter:

{
  "name": "&NAME!JSON.",
  "tags": [
    {loop ","/}"&APEX$ITEM!JSON."{if APEX$INDEX!=APEX$COUNT/},{endif/}{endloop/}
  ]
}

With P30_TAGS = "red,green,blue", the body renders as:

{
  "name": "Widget",
  "tags": ["red","green","blue"]
}

APEX$ITEM is the current value inside the loop; APEX$INDEX and APEX$COUNT let the template add the comma separator only between items, not after the last one. The same shape works for any delimited input — the delimiter is the parameter to {loop/}.

Pattern 3 — Reusable partials with {with/} and {apply/}.

A POST that creates an order with line items, where each line item shares the same shape. The body declares a named partial once and applies it for each line:

{
  "order_id": "&ORDER_ID!JSON.",
  "lines": [
    {loop "|"/}
      {with/}
        SKU := &APEX$ITEM!JSON.
      {apply LINE_ITEM_BODY/}
      {if APEX$INDEX!=APEX$COUNT/},{endif/}
    {endloop/}
  ]
}

The LINE_ITEM_BODY named template is defined alongside the REST source and is reused wherever the same shape is needed. This is the most advanced of the three patterns and pays off when the same nested structure appears more than once — keep it on the shelf until then.

Migration note

Existing Request Body templates continue to function unchanged. Template directives are opt-in: a body that uses only #PARAM# substitution renders in 26.1 exactly as it did in 26.0. There is no need to rewrite a working template just to be on the new version.

Putting them together

One scenario that uses both features. A POST to an external order-tracking API. The body needs three things: a requested_at timestamp computed at the moment the request goes out, a metadata.tenant field included only when the session has a tenant context, and a tags array built from a comma-delimited page item.

The parameter table on the operation:

ParameterDefault TypeValue
REQUESTED_ATExpression (PL/SQL)to_char(systimestamp, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')
TENANTFunction Body (PL/SQL)reads sys_context('apex$session', 'tenant_id'), returns null when unset
TAGSPage ItemP40_TAGS

The body template:

{
  "order_id": "&ORDER_ID!JSON.",
  "requested_at": "&REQUESTED_AT!JSON."
  {if TENANT/},
  "metadata": { "tenant": "&TENANT!JSON." }
  {endif/}
  {if TAGS/},
  "tags": [
    {loop ","/}"&APEX$ITEM!JSON."{if APEX$INDEX!=APEX$COUNT/},{endif/}{endloop/}
  ]
  {endif/}
}

Submitted with ORDER_ID = "ORD-1042", P40_TAGS = "rush,gift", and no tenant context, the body renders as:

{
  "order_id": "ORD-1042",
  "requested_at": "2026-05-25T09:14:22Z",
  "tags": ["rush","gift"]
}

The metadata block is absent because the function returned null; the tags array is built from the page item; the timestamp is computed at invocation time. Before 26.1 this was a Before Header process, a hidden page item, and an apex_web_service.make_rest_request call in a PL/SQL region. In 26.1 it is a parameter table and a body template — two declarative panels.

Why this matters for AI-generated apps

LLMs are reliable when the target is declarative metadata and unreliable when the target is PL/SQL. The gap between what an LLM can author against a Builder spec and what it can produce in a freeform language is wide, and it is where the bugs in AI-generated APEX code tend to cluster.

Every gap in the declarative surface used to push code generators back into PL/SQL workarounds. The 26.1 REST Data Source enhancements close two of the most common ones. A parameter that needs a dynamic default is now expressible as a SQL query, an expression, or a function body inside the operation editor — no surrounding page process to invent. A request body that needs conditional fields or array construction is now expressible inside the template — no post-render manipulation.

This is part of a broader pattern in 26.1. APEXlang, the AI Agents feature, and the natural-language-to-intermediate-representation work all push more of APEX's behavior into a form that an LLM can generate without leaving the declarative model. The REST Data Source work is the same direction applied to integrations.

Posts on APEXlang and AI Agents are coming.

Where to learn more

  • The official 26.1 release notes, Section 2.5 ("REST Data Sources"): https://docs.oracle.com/en/database/oracle/apex/26.1/htmrn/new-features.html
  • The APEX App Builder User's Guide, chapter on REST Data Sources — the operation editor sections cover the new Default Type picker and the directive syntax in detail. The exact URL changes with each release; navigate from the docs index at https://docs.oracle.com/en/database/oracle/apex/.
  • The Oracle APEX category on the Oracle Forums is where edge cases and migration questions get worked out: https://forums.oracle.com/ords/apexds/domain/dev-community/category/apex
  • If a workflow you maintain becomes shorter with these enhancements — or stubbornly does not — drop a note to hi@ahmamed.dev. The next post benefits from the example.