How to Create APEX Sessions and Manage Session State from PL/SQL

A tested guide to APEX_SESSION and APEX_SESSION_STATE in Oracle APEX, from sessions in jobs and scripts to typed item values and their traps.

Inside an Oracle APEX application, PL/SQL always runs in a session: a process, computation, or validation can read APP_USER and page items without thinking about it. Outside a request, in a scheduler job, a test script, or a SQL Developer worksheet, there is no session, and many APEX APIs fail or return nothing. APEX_SESSION fixes that by creating, attaching to, and deleting sessions from PL/SQL. APEX_SESSION_STATE then reads and writes item values with proper data types, where v and apex_util.set_session_state only know strings.

This guide covers both packages with tested examples and their real output, including two format-mask traps in APEX 26.1 that make GET_NUMBER and GET_TIMESTAMP fail, and a 32,767-character limit the documentation does not mention.

Quick Reference

TaskProcedure or function
Start and end a session outside a requestAPEX_SESSION.CREATE_SESSION, DELETE_SESSION
Continue an existing session laterAPEX_SESSION.ATTACH, DETACH
Debug, trace, and tenant for a sessionAPEX_SESSION.SET_DEBUG, SET_TRACE, SET_TENANT_ID
Set an item's value with a data typeAPEX_SESSION_STATE.SET_VALUE
Read an item's value as a typeAPEX_SESSION_STATE.GET_VARCHAR2, GET_NUMBER, GET_TIMESTAMP, GET_BOOLEAN, GET_CLOB
Read an item's value with its typeAPEX_SESSION_STATE.GET_VALUE

How to Run These Examples

The examples ran in Oracle APEX 26.1 against a test application with ID 200, whose page 20 holds sample items such as P20_TEXT, P20_NUMBER, P20_DATE, and P20_SWITCH. Run them as the application's parsing schema in SQL Developer, SQLcl, or SQL*Plus with server output switched on. Do not use SQL Workshop's SQL Commands or SQL Scripts: APEX does not support creating sessions there.

Replace the application ID, page, user, and item names with your own. Examples that say they need a session expect one to exist already; create it first with APEX_SESSION.CREATE_SESSION, exactly as the first example does, and delete it when you are done. The output under each example is exactly what the database printed.

Creating and Using Sessions: APEX_SESSION

CREATE_SESSION and DELETE_SESSION

CREATE_SESSION creates a session of an application for a user and sets up the environment as a request would, including APP_ID, APP_USER, and page items; it also runs the application's Initialization PL/SQL Code. DELETE_SESSION deletes a session, the current one by default, after running the Cleanup PL/SQL Code. Use them in scripts, jobs, and tests that call APEX APIs outside a web request.

Syntax:

apex_session.create_session(
    p_app_id                   in number,
    p_page_id                  in number,
    p_username                 in varchar2,
    p_call_post_authentication in boolean default false)

apex_session.delete_session(
    p_session_id in number default apex_application.g_instance)
ParameterDescription
p_app_id, p_page_idThe application and page.
p_usernameThe session's user. It is not authenticated.
p_call_post_authenticationAlso run the authentication scheme's post-authentication procedure.
p_session_idThe session to delete.

Example:

begin
    apex_session.create_session(
        p_app_id   => 200,
        p_page_id  => 1,
        p_username => 'ADMIN');
    dbms_output.put_line('app ' || v('APP_ID') || ', page ' || v('APP_PAGE_ID')
        || ', user ' || v('APP_USER') || ', session ' || v('APP_SESSION'));
    -- the application's Initialization PL/SQL Code and computations ran:
    dbms_output.put_line('pending approvals: ' || v('G_PENDING_APPROVALS'));
    apex_session.delete_session;
    dbms_output.put_line('after delete_session: session ' || nvl(v('APP_SESSION'), '(none)'));
end;
/

Output:

app 200, page 1, user ADMIN, session 11537280069438
pending approvals:
after delete_session: session (none)

The pending approvals item is empty because the application process that fills it runs Before Header when page 1 is requested, not when a session is created. Code that relies on page-rendering processes will not see their results in a session made this way. Note also that p_username is taken on trust: the session belongs to that user without any password check, so keep scripts that create sessions away from anything a user can influence.

ATTACH and DETACH

ATTACH joins an existing session, for example to continue its work in a later database call such as a job the session started. DETACH leaves the session, running the cleanup code but keeping the session and its state.

Syntax:

apex_session.attach(p_app_id in number, p_page_id in number, p_session_id in number)
apex_session.detach

Example:

declare
    l_session_id number;
begin
    apex_session.create_session(p_app_id => 200, p_page_id => 1, p_username => 'ADMIN');
    l_session_id := v('APP_SESSION');
    apex_util.set_session_state('P20_TEXT', 'kept in the session');
    apex_session.detach;                                  -- the session still exists
    dbms_output.put_line('detached: session ' || nvl(v('APP_SESSION'), '(none)'));

    apex_session.attach(p_app_id => 200, p_page_id => 20, p_session_id => l_session_id);
    dbms_output.put_line('attached again: P20_TEXT = ' || v('P20_TEXT'));
    apex_session.delete_session(p_session_id => l_session_id);
end;
/

Output:

detached: session (none)
attached again: P20_TEXT = kept in the session

The value set before DETACH was still there after ATTACH, because detaching leaves the session in place. A common pattern is for a page process to pass its session ID to a background job, which attaches to it to read the user's items and write results back.

SET_DEBUG, SET_TRACE, and SET_TENANT_ID

SET_DEBUG turns debug logging on for a session's future requests, at a level from 1 to 9, or off with null. SET_TRACE turns on SQL trace with 'SQL', or off with null. Both need a commit to take effect. SET_TENANT_ID sets the session's tenant, which APP_TENANT_ID then returns, for multitenant applications; it can be set only once per session.

Syntax:

apex_session.set_debug(p_session_id in number default apex_application.g_instance, p_level in apex_debug.t_log_level)
apex_session.set_trace(p_session_id in number default apex_application.g_instance, p_mode in varchar2)
apex_session.set_tenant_id(p_tenant_id in varchar2)

This example needs a session of application 200, page 1, for the user ADMIN. The second SET_TENANT_ID call fails on purpose, to show the once-per-session rule.

Example:

begin
    apex_session.set_debug(p_level => apex_debug.c_log_level_info);   -- this session's next requests
    apex_session.set_trace(p_mode => 'SQL');
    apex_session.set_tenant_id(p_tenant_id => 'ORBIT-EU');
    dbms_output.put_line('APP_TENANT_ID: ' || v('APP_TENANT_ID'));
    begin
        apex_session.set_tenant_id(p_tenant_id => 'ORBIT-US');
    exception
        when others then dbms_output.put_line('second set_tenant_id: ' || substr(sqlerrm, 1, 72));
    end;
    commit;                                               -- the settings are stored at commit
end;
/

Output:

APP_TENANT_ID: ORBIT-EU
second set_tenant_id: ORA-20987: APEX - The tenant id already exists for the current session.

SET_DEBUG is handy for switching on debugging for one user's session from the back end, when a problem only shows up for them. Reading the resulting log is covered in the guide to debugging, source control, and going to production in Oracle APEX.

Typed Session State: APEX_SESSION_STATE

APEX_SESSION_STATE reads and writes the values of page and application items with a data type. The older v function and apex_util.get_session_state and set_session_state only handle strings. For the basics of session state and how items get their values, see the guide to session state and substitution strings in Oracle APEX.

SET_VALUE

Sets an item's value from a VARCHAR2, NUMBER, BOOLEAN, or CLOB, through four overloads, and commits by default.

Syntax:

apex_session_state.set_value(
    p_item   in varchar2,
    p_value  in varchar2 | number | boolean | clob,
    p_commit in boolean default true)

GET_VARCHAR2, GET_NUMBER, GET_TIMESTAMP, GET_BOOLEAN, and GET_CLOB

Return an item's value as a given type. GET_VARCHAR2 is the same as v. GET_NUMBER and GET_TIMESTAMP convert using the item's format mask, or the session's number and date formats when the item has none.

Syntax:

apex_session_state.get_varchar2(p_item in varchar2) return varchar2
apex_session_state.get_number(p_item in varchar2) return number
apex_session_state.get_timestamp(p_item in varchar2) return timestamp
apex_session_state.get_boolean(p_item in varchar2) return boolean
apex_session_state.get_clob(p_item in varchar2) return clob

This example needs a session of application 200, page 20. Its last call fails on purpose, and the exception handler prints the error.

Example:

begin
    apex_session_state.set_value('P20_TEXT', 'Trailblazer 2-Person Tent');
    apex_session_state.set_value('P20_NUMBER', '1,249.50');          -- in the item's format mask
    apex_session_state.set_value('P20_DATE', '23-SEP-2026');         -- in the session's date format
    apex_session_state.set_value('P20_SWITCH', true);                -- BOOLEAN
    dbms_output.put_line('get_varchar2(P20_TEXT):  ' || apex_session_state.get_varchar2('P20_TEXT'));
    dbms_output.put_line('get_number(P20_NUMBER):  ' || apex_session_state.get_number('P20_NUMBER'));
    dbms_output.put_line('get_timestamp(P20_DATE): ' || to_char(apex_session_state.get_timestamp('P20_DATE'), 'YYYY-MM-DD HH24:MI'));
    dbms_output.put_line('get_boolean(P20_SWITCH): ' || case apex_session_state.get_boolean('P20_SWITCH') when true then 'TRUE' else 'FALSE' end);
    apex_session_state.set_value('P20_NUMBER', 1249.5);              -- a NUMBER is stored as is...
    dbms_output.put_line('v(P20_NUMBER):           ' || v('P20_NUMBER'));
    dbms_output.put_line('get_number(P20_NUMBER):  ' || apex_session_state.get_number('P20_NUMBER'));   -- ...and fails
exception
    when value_error then dbms_output.put_line('get_number: ' || sqlerrm);
end;
/

Output:

get_varchar2(P20_TEXT):  Trailblazer 2-Person Tent
get_number(P20_NUMBER):  1249.5
get_timestamp(P20_DATE): 2026-09-23 00:00
get_boolean(P20_SWITCH): TRUE
v(P20_NUMBER):           1249.5
get_number: ORA-06502: PL/SQL: value or conversion error

The failure is the first trap. SET_VALUE with a NUMBER stores it without the item's format mask, as 1249.5, and GET_NUMBER then cannot read it back through the mask 999G999G990D00. Set numbers as strings in the item's own format, as 1,249.50 here, and GET_NUMBER works. The second trap is similar: SET_VALUE with a TIMESTAMP stores it in the session's timestamp format, which GET_TIMESTAMP does not read for an item without a format mask. Give date items a format mask and set them as strings in that mask.

GET_VALUE

Returns the value as a record of type t_value, with the fields data_type, varchar2_value, clob_value, and boolean_value, of which one is filled.

Syntax:

apex_session_state.get_value(p_item in varchar2) return apex_session_state.t_value

This example needs a session of application 200, page 20.

Example:

declare
    l_value apex_session_state.t_value;
begin
    apex_session_state.set_value('P20_TEXTAREA', to_clob(rpad('x', 32767, 'x')) || rpad('y', 8000, 'y'), p_commit => false);
    dbms_output.put_line('get_clob:     ' || length(apex_session_state.get_clob('P20_TEXTAREA')) || ' characters');
    dbms_output.put_line('get_varchar2: ' || length(apex_session_state.get_varchar2('P20_TEXTAREA')) || ' characters');
    l_value := apex_session_state.get_value('P20_TEXTAREA');
    dbms_output.put_line('get_value:    varchar2_value ' || length(l_value.varchar2_value)
        || ' characters, clob_value ' || nvl(to_char(length(l_value.clob_value)), '(null)'));
    apex_session_state.set_value('P20_SWITCH', true);
    l_value := apex_session_state.get_value('P20_SWITCH');
    dbms_output.put_line('get_value(P20_SWITCH): boolean_value '
        || case l_value.boolean_value when true then 'TRUE' when false then 'FALSE' else '(null)' end);
end;
/

Output:

get_clob:     32767 characters
get_varchar2: 32767 characters
get_value:    varchar2_value 32767 characters, clob_value (null)
get_value(P20_SWITCH): boolean_value TRUE

The value set was 40,767 characters long, but every read returned only 32,767. In 26.1, a value longer than 32,767 characters is kept as a VARCHAR2 of that length, so GET_CLOB, GET_VARCHAR2, and GET_VALUE all return its first 32,767 characters, and clob_value stays empty. The documentation says GET_VARCHAR2 raises an exception for a CLOB value; it does not, it silently truncates. Keep long values in an APEX collection instead of an item.

For setting items from PL/SQL inside an application, the simpler route is often a computation or a process, as shown in setting a page item value using PL/SQL.

Conclusion

APEX_SESSION gives code outside a request the context APEX APIs need. CREATE_SESSION sets up a session for a user, running the application's initialization code but not page processes; DELETE_SESSION removes it; ATTACH and DETACH let a later call, such as a background job, continue an existing session; and SET_DEBUG, SET_TRACE, and SET_TENANT_ID configure it. APEX_SESSION_STATE reads and writes item values with real data types. Set numbers and dates as strings in the item's format mask, or GET_NUMBER and GET_TIMESTAMP fail, and keep values longer than 32,767 characters in a collection, because longer item values are silently cut.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE and software veteran with 25+ years of experience, passionate about AI and IT innovation.

guest

0 Comments
Oldest
Newest Most Voted
00