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.
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.
In 26.1, the Default Value field becomes a type-aware picker. The release notes describe five options for each parameter default:
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.
Static. The original behavior, kept for the cases that never needed anything else. A literal API key in a header parameter:
sk_live_3f7c9a2b1d4e8f6aUseful 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_TERMThis 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 common pattern before 26.1, for an interactive report backed by a REST source that supports ?updated_since=:
P20_LAST_SYNCED_AT.max(synced_at) from the local log table and assigns it to the item.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:
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.
nvl, coalesce, or a union all with a sentinel row).&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.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:
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.
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 partialAlongside 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 bodiesThe 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.
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.
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.
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:
| Parameter | Default Type | Value |
|---|---|---|
REQUESTED_AT | Expression (PL/SQL) | to_char(systimestamp, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') |
TENANT | Function Body (PL/SQL) | reads sys_context('apex$session', 'tenant_id'), returns null when unset |
TAGS | Page Item | P40_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.
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.
hi@ahmamed.dev. The next post benefits from the example.