How to Remember Each User's Filters with Preferences in Oracle APEX

Set filter items to Per User (Persistent) storage, refresh the report with a dynamic action, add a Reset Filters button, and check the saved preferences in SQL.

Report pages often start with a few filter fields, such as a team and a status. Most users pick the same filters every day, and it is annoying when the page forgets them after each sign-in. A page that remembers the last choice of each user saves those clicks.

Oracle APEX can do this without code. When an item's Session State Storage is set to Per User (Persistent), APEX saves its value as a user preference and loads it again in the next session. This guide builds a filtered report with two such items, adds a Reset Filters button, and checks where APEX stores the values.

The guide uses service requests as the example; the same steps work for any report with filter items. The guide How to Manage Users, Preferences, and Caches Using APEX_UTIL covers the PL/SQL preference calls. This guide does the same job declaratively, with item settings and a Clear Session State process.

Step 1: Create the Tables

The example needs two tables. SUPPORT_TEAMS holds the teams. SERVICE_REQUESTS holds the requests, and its TEAM_ID column is a foreign key to SUPPORT_TEAMS.

Create the tables, the indexes and the sample data:

Example:

create table support_teams (
  team_id     number generated by default on null as identity,
  team_name   varchar2(60)  not null,
  constraint support_teams_pk primary key (team_id),
  constraint support_teams_name_uk unique (team_name)
);

create table service_requests (
  request_id  number generated by default on null as identity,
  subject     varchar2(200) not null,
  team_id     number        not null,
  status      varchar2(20)  default on null 'OPEN' not null,
  priority    varchar2(10)  default on null 'NORMAL' not null,
  opened_on   date          default on null sysdate not null,
  constraint service_requests_pk primary key (request_id),
  constraint service_requests_team_fk foreign key (team_id)
    references support_teams (team_id),
  constraint service_requests_status_ck
    check (status in ('OPEN', 'IN_PROGRESS', 'CLOSED')),
  constraint service_requests_priority_ck
    check (priority in ('LOW', 'NORMAL', 'HIGH'))
);

create index service_requests_team_ix on service_requests (team_id);
create index service_requests_status_ix on service_requests (status);

insert into support_teams (team_name) values ('Network');
insert into support_teams (team_name) values ('Hardware');
insert into support_teams (team_name) values ('Accounts');

insert into service_requests (subject, team_id, status, priority, opened_on)
values ('VPN drops every hour', 1, 'OPEN', 'HIGH', date '2026-10-12');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('Wi-Fi slow in meeting room B', 1, 'IN_PROGRESS', 'NORMAL', date '2026-10-10');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('Firewall rule for new server', 1, 'CLOSED', 'NORMAL', date '2026-10-01');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('Laptop battery swells', 2, 'OPEN', 'HIGH', date '2026-10-13');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('Second monitor request', 2, 'OPEN', 'LOW', date '2026-10-11');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('Printer jam on floor 2', 2, 'CLOSED', 'NORMAL', date '2026-10-03');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('Reset password for shared mailbox', 3, 'OPEN', 'NORMAL', date '2026-10-14');
insert into service_requests (subject, team_id, status, priority, opened_on)
values ('New hire account setup', 3, 'IN_PROGRESS', 'HIGH', date '2026-10-09');
commit;

select r.request_id, r.subject, t.team_name, r.status, r.priority
from   service_requests r join support_teams t on t.team_id = r.team_id
order  by r.request_id;

Output:

Table SUPPORT_TEAMS created.

Table SERVICE_REQUESTS created.

Index SERVICE_REQUESTS_TEAM_IX created.

Index SERVICE_REQUESTS_STATUS_IX created.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

Commit complete.

   REQUEST_ID SUBJECT                              TEAM_NAME    STATUS         PRIORITY
_____________ ____________________________________ ____________ ______________ ___________
            1 VPN drops every hour                 Network      OPEN           HIGH
            2 Wi-Fi slow in meeting room B         Network      IN_PROGRESS    NORMAL
            3 Firewall rule for new server         Network      CLOSED         NORMAL
            4 Laptop battery swells                Hardware     OPEN           HIGH
            5 Second monitor request               Hardware     OPEN           LOW
            6 Printer jam on floor 2               Hardware     CLOSED         NORMAL
            7 Reset password for shared mailbox    Accounts     OPEN           NORMAL
            8 New hire account setup               Accounts     IN_PROGRESS    HIGH

8 rows selected.
  • SERVICE_REQUESTS_TEAM_FK allows only existing teams. The two check constraints limit STATUS and PRIORITY to the listed values.
  • STATUS, PRIORITY and OPENED_ON use DEFAULT ON NULL, so a new request gets OPEN, NORMAL and the current date without a trigger.
  • The indexes on TEAM_ID and STATUS support the two filters of the report.

Step 2: Create a Blank Page

In App Builder, open the application and click Create Page. On the Component tab, click Blank Page:

Create a Page wizard with the Blank Page tile marked with a red box
The Blank Page tile on the Component tab.

Enter Page Number 370 and Name Service Requests. Turn off Use Breadcrumb and Use Navigation, and click Create Page:

Create Blank Page dialog with Page Number 370, Name Service Requests and the two navigation switches marked with red boxes
The page number, the name and the navigation settings.

APEX opens the new page in Page Designer.

Step 3: Add the Filters Region

In the Rendering tree, right-click Body and click Create Region. Set Name to Filters and keep Type Static Content:

Page Designer with the Filters region selected and Name and Type marked with red boxes
A Static Content region holds the filter items.

Step 4: Add the Team Filter Item

Right-click the Filters region and click Create Page Item. Set these properties:

  • Identification: Name P370_TEAM_ID, Type Select List.
  • Label: Team.
  • List of Values: Type SQL Query, Null Display Value "- All Teams -".

Enter this SQL Query for the list of values:

Example:

select team_name, team_id from support_teams order by team_name
Property Editor of P370_TEAM_ID with Name, Type, Label, List of Values Type, SQL Query and Null Display Value marked with red boxes
The Team select list with an empty "All Teams" entry.

Now scroll to Session State and set Storage to Per User (Persistent):

Session State group of P370_TEAM_ID with Storage set to Per User (Persistent) marked with a red box
This one setting makes APEX remember the value per user.

Storage has three choices: Per Request (Memory Only), Per Session (Persistent) and Per User (Persistent). Only the last one saves the value as a preference of the signed-in user, so it comes back in a later session.

Step 5: Add the Status Filter Item

Create a second item in the Filters region with these properties:

  • Identification: Name P370_STATUS, Type Select List.
  • Label: Status.
  • List of Values: Type Static Values, Null Display Value "- All Statuses -".
  • Layout: turn off Start New Row, so the item sits next to Team.

Click the Static Values property and enter three entries: display Open with return OPEN, In Progress with IN_PROGRESS, and Closed with CLOSED.

Property Editor of P370_STATUS with Name, Type, Label, Static Values, Null Display Value and Start New Row marked with red boxes
The Status select list with three static values.

Set Session State Storage to Per User (Persistent) for this item too:

Session State group of P370_STATUS with Storage set to Per User (Persistent) marked with a red box
Both filter items are stored per user.

Step 6: Add the Report Region

Right-click Body and click Create Region again. Set Name to Requests and Type to Classic Report. Under Source, keep Location Local Database and Type SQL Query, and enter this query:

Example:

select r.request_id,
       r.subject,
       t.team_name,
       r.status,
       r.priority,
       r.opened_on
  from service_requests r
  join support_teams t on t.team_id = r.team_id
 where (:P370_TEAM_ID is null or r.team_id = :P370_TEAM_ID)
   and (:P370_STATUS is null or r.status = :P370_STATUS)
 order by r.opened_on desc

Set Page Items to Submit to P370_TEAM_ID,P370_STATUS:

Property Editor of the Requests region with Name, Type, SQL Query and Page Items to Submit marked with red boxes
The report reads both filter items.

An empty filter matches all rows, because each condition starts with "item is null or". Page Items to Submit sends the current item values to the server when the report refreshes.

Step 7: Refresh the Report When a Filter Changes

Open the Dynamic Actions tab. Right-click Change and click Create Dynamic Action. Set Name to Filter changed. Under When, keep Event Change, set Selection Type Item(s), and enter P370_TEAM_ID,P370_STATUS in Item(s):

Dynamic action Filter changed with Name, Event, Selection Type and Item(s) marked with red boxes
The dynamic action fires when either filter changes.

Under True, click the action that APEX created with the dynamic action (Show). Set Action to Refresh, Selection Type to Region and Region to Requests:

Refresh action with Action, Selection Type and Region marked with red boxes
The Refresh action reloads only the report region.

The page does not need a submit. When the report refreshes, APEX receives the new item values through Page Items to Submit and saves them as preferences at the same time. The test in Step 10 shows this.

Step 8: Add the Reset Filters Button

Right-click the Filters region and click Create Button. Set Button Name to RESET_FILTERS, Label to Reset Filters and Slot to Next. Keep Action Submit Page:

Property Editor of the RESET_FILTERS button with Button Name, Label, Region, Slot and Action marked with red boxes
The button submits the page with the request RESET_FILTERS.

Step 9: Add the Reset Process

Open the Processing tab. Right-click Processing and click Create Process. Set Name to Reset filters and Type to Clear Session State. Under Clear, keep Type Clear all Items on the Current Page. Under Server-side Condition, set When Button Pressed to RESET_FILTERS:

Process Reset filters with Name, Type, Clear Type and When Button Pressed marked with red boxes
The process clears the page items only when Reset Filters is clicked.

Click Save. Clearing a Per User item also clears its saved preference, as the next step shows. That is why a separate process for the preferences is not needed.

Step 10: Test the Page

Run the page as DEMO_DEV. Select Team Hardware and Status Open. The report refreshes and shows two requests:

Service Requests page for demo_dev with Team Hardware and Status Open marked with a red box and two rows in the report
DEMO_DEV filters the report without a page submit.

Sign out, sign in again as DEMO_DEV, and open the page. The new session starts with Hardware and Open, and the report already shows the same two rows:

Service Requests page after a new sign-in with Team Hardware and Status Open restored and marked with a red box
The filters come back after signing in again.

Now sign in as SALES_USER. This user sees "All Teams" and "All Statuses" and all eight rows. The choice of DEMO_DEV does not affect other users:

Service Requests page for sales_user with no filters set and all eight requests listed
Each user has separate filter values.

In this example, SALES_USER then picked Team Network. When DEMO_DEV signed in again afterwards, the page still showed Hardware and Open, not Network.

APEX stores the values as workspace preferences. Query APEX_WORKSPACE_PREFERENCES to see them:

Example:

select user_name, preference_name, preference_type
from   apex_workspace_preferences
where  preference_name like 'PERSISTENT_ITEM_P370%'
order  by user_name, preference_name;

Output:

USER_NAME     PREFERENCE_NAME                 PREFERENCE_TYPE
_____________ _______________________________ __________________________
DEMO_DEV      PERSISTENT_ITEM_P370_STATUS     PROTECTED_SESSION_STATE
DEMO_DEV      PERSISTENT_ITEM_P370_TEAM_ID    PROTECTED_SESSION_STATE
SALES_USER    PERSISTENT_ITEM_P370_TEAM_ID    PROTECTED_SESSION_STATE

Each preference is named PERSISTENT_ITEM_ followed by the item name. SALES_USER has only a TEAM_ID preference, because that user never changed Status. The type is PROTECTED_SESSION_STATE, and in this test the view returned PREFERENCE_VALUE empty for these rows.

To read the actual values, call APEX_UTIL.GET_PREFERENCE inside an APEX session of the user:

Example:

set serveroutput on
begin
  apex_session.create_session(
    p_app_id   => 400,
    p_page_id  => 370,
    p_username => 'DEMO_DEV');
  dbms_output.put_line('Team ID: ' || apex_util.get_preference('PERSISTENT_ITEM_P370_TEAM_ID'));
  dbms_output.put_line('Status : ' || apex_util.get_preference('PERSISTENT_ITEM_P370_STATUS'));
  apex_session.delete_session;
end;
/

Output:

Team ID: 2
Status : OPEN

PL/SQL procedure successfully completed.

Team ID 2 is the Hardware team, and the status is OPEN, as selected on the page.

Step 11: Test the Reset Button

Sign in as DEMO_DEV, open the page, and click Reset Filters. The page submits, the process clears both items, and the report shows all eight rows again:

Service Requests page after Reset Filters with All Teams and All Statuses and all eight rows
Reset Filters returns the page to its default state.

Run the two scripts of Step 10 again. The output:

Output:

Team ID:
Status :

PL/SQL procedure successfully completed.

USER_NAME     PREFERENCE_NAME                 PREFERENCE_TYPE
_____________ _______________________________ __________________________
DEMO_DEV      PERSISTENT_ITEM_P370_STATUS     PROTECTED_SESSION_STATE
DEMO_DEV      PERSISTENT_ITEM_P370_TEAM_ID    PROTECTED_SESSION_STATE
SALES_USER    PERSISTENT_ITEM_P370_TEAM_ID    PROTECTED_SESSION_STATE

The preferences of DEMO_DEV still exist, but they no longer hold a value. After the next sign-in, the page opened with "All Teams" and "All Statuses".

Things to Know

  • The values belong to the signed-in user (APP_USER). Each user gets separate values, as the SALES_USER test showed.
  • The preference name contains only the item name, such as PERSISTENT_ITEM_P370_TEAM_ID, not the application ID. Give persistent items clear page-based names.
  • A Refresh dynamic action with Page Items to Submit is enough to save the preference. The page does not have to be submitted.
  • A Clear Session State process on the item clears the saved preference too, so the reset needs no PL/SQL.
  • APEX_WORKSPACE_PREFERENCES lists these preferences, but in this test it did not show their values. Use APEX_UTIL.GET_PREFERENCE in an APEX session to read them.
  • The page keeps the default Page Access Protection "Arguments Must Have Checksum". In this example, a hand-made URL that set P370_TEAM_ID or cleared page 370 failed with "Session state protection violation", and the saved filter stayed unchanged.

Related Guides

Conclusion

Remembering each user's filters in Oracle APEX takes one property: set Session State Storage of the filter items to Per User (Persistent). APEX then saves the values as user preferences and restores them in the next session.

A dynamic action with Page Items to Submit keeps the report in sync without a page submit, and a Clear Session State process gives users a Reset Filters button. The tests showed that the values survive a new sign-in, stay separate for each user, and are cleared by the reset.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted