How to Build a Three-Level Master-Detail Page with Interactive Grids in Oracle APEX

Chain three Interactive Grids with Master Region and Master Column, add a page Save button, and store a new parent with its children and grandchildren in one submit.

Some data has more than one level of children. A project has tasks, and each task has checklist items. An order has lines, and each line has deliveries. Users want to see and edit all three levels on one page, without opening a new page for every task.

Oracle APEX can link any number of Interactive Grids with the Master Region property. This guide builds a page with three editable grids, where the second grid follows the first and the third follows the second. One Save button stores new and changed rows of all three levels in a single submit.

The guide How to Build an Orders and Order Lines Master-Detail Page in Oracle APEX creates two linked grids with the Master Detail wizard. That wizard stops at two levels. This guide builds the grids by hand in Page Designer, adds the third level, and shows what is needed to save a new parent and its new children together.

The example uses projects, tasks and checklist items; the same steps work for any three tables linked by foreign keys.

Step 1: Create the Tables

Three tables hold the data:

  • PLAN_PROJECTS is the top level, with one row per project.
  • PLAN_TASKS has the tasks of each project.
  • PLAN_TASK_ITEMS has the checklist items of each task.

Create the tables, the indexes and the sample data:

Example:

create table plan_projects (
  project_id    number generated by default on null as identity
                constraint plan_projects_pk primary key,
  project_name  varchar2(100) not null
                constraint plan_projects_name_uk unique,
  owner_name    varchar2(60) not null,
  due_date      date
);

create table plan_tasks (
  task_id     number generated by default on null as identity
              constraint plan_tasks_pk primary key,
  project_id  number not null
              constraint plan_tasks_project_fk references plan_projects on delete cascade,
  task_name   varchar2(100) not null,
  status      varchar2(12) default on null 'OPEN' not null
              constraint plan_tasks_status_ck check (status in ('OPEN', 'IN PROGRESS', 'DONE')),
  constraint plan_tasks_name_uk unique (project_id, task_name)
);

create index plan_tasks_project_ix on plan_tasks (project_id);

create table plan_task_items (
  item_id    number generated by default on null as identity
             constraint plan_task_items_pk primary key,
  task_id    number not null
             constraint plan_task_items_task_fk references plan_tasks on delete cascade,
  item_text  varchar2(200) not null,
  is_done    varchar2(1) default on null 'N' not null
             constraint plan_task_items_done_ck check (is_done in ('Y', 'N'))
);

create index plan_task_items_task_ix on plan_task_items (task_id);

insert into plan_projects (project_name, owner_name, due_date)
values ('Office Move', 'Anita Rao', date '2026-12-15');
insert into plan_projects (project_name, owner_name, due_date)
values ('Website Redesign', 'Daniel Kim', date '2027-02-28');

insert into plan_tasks (project_id, task_name, status) values (1, 'Book movers', 'DONE');
insert into plan_tasks (project_id, task_name, status) values (1, 'Set up network', 'IN PROGRESS');
insert into plan_tasks (project_id, task_name) values (2, 'Write content plan');

insert into plan_task_items (task_id, item_text, is_done) values (1, 'Get three quotes', 'Y');
insert into plan_task_items (task_id, item_text, is_done) values (1, 'Sign the contract', 'Y');
insert into plan_task_items (task_id, item_text) values (2, 'Order switches');
insert into plan_task_items (task_id, item_text) values (2, 'Test Wi-Fi coverage');
insert into plan_task_items (task_id, item_text) values (3, 'List all current pages');

commit;

select p.project_name, t.task_name, t.status, i.item_text, i.is_done
from plan_projects p
join plan_tasks t on t.project_id = p.project_id
join plan_task_items i on i.task_id = t.task_id
order by p.project_id, t.task_id, i.item_id;

Output:

Table PLAN_PROJECTS created.

Table PLAN_TASKS created.

Index PLAN_TASKS_PROJECT_IX created.

Table PLAN_TASK_ITEMS created.

Index PLAN_TASK_ITEMS_TASK_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.

Commit complete.

PROJECT_NAME        TASK_NAME             STATUS         ITEM_TEXT                 IS_DONE
___________________ _____________________ ______________ _________________________ __________
Office Move         Book movers           DONE           Get three quotes          Y
Office Move         Book movers           DONE           Sign the contract         Y
Office Move         Set up network        IN PROGRESS    Order switches            N
Office Move         Set up network        IN PROGRESS    Test Wi-Fi coverage       N
Website Redesign    Write content plan    OPEN           List all current pages    N

The keys are what the page is built on:

  • Each table has an identity primary key: PLAN_PROJECTS_PK, PLAN_TASKS_PK and PLAN_TASK_ITEMS_PK. The grids use them to find the rows they update and delete.
  • PLAN_TASKS_PROJECT_FK links each task to a project, and PLAN_TASK_ITEMS_TASK_FK links each item to a task. ON DELETE CASCADE removes the tasks and items of a deleted project.
  • PLAN_TASKS_PROJECT_IX and PLAN_TASK_ITEMS_TASK_IX index the foreign keys. The lower grids query by these columns every time the user selects a row above.
  • PLAN_PROJECTS_NAME_UK keeps project names unique, and PLAN_TASKS_NAME_UK allows a task name only once per project.
  • STATUS and IS_DONE use DEFAULT ON NULL and check constraints, so no trigger is needed to fill or check them.

Step 2: Create a Blank Page

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

Create a Page wizard with the Blank Page tile marked with a red box
Choose the Blank Page type.

Set Page Number to 980 and Name to Project Checklist. Turn Use Breadcrumb off, keep Use Navigation on, and set Icon to fa-sitemap. Click Create Page:

Create Blank Page screen with Page Number 980, Name Project Checklist, Use Breadcrumb off, Use Navigation on and Icon fa-sitemap marked with red boxes
The page number, name and navigation entry.

APEX opens the new page in Page Designer. The next steps add the three grids to its Body.

Step 3: Add the Projects Grid

In the Rendering tree, right-click Body and choose Create Region. Set these properties:

  • Identification, Name: Projects
  • Identification, Type: Interactive Grid
  • Source, Table Name: PLAN_PROJECTS
Page Designer with the Projects region selected; Name Projects, Type Interactive Grid and Table Name PLAN_PROJECTS marked with red boxes
The Projects grid on the PLAN_PROJECTS table.

A new Interactive Grid region gets the Interactive Report template, which hides the region title. Under Appearance, set Template to Standard, so the page shows the headings Projects, Tasks and Checklist Items:

Appearance group of the Projects region with Template Standard marked with a red box
The Standard template shows the region title.

Open the Attributes tab of the region. Under Edit, turn Enabled on. Under Toolbar, uncheck Save Button in Controls. The page gets its own Save button in Step 7, and that step explains why the grid buttons must go:

Attributes tab of the Projects region with Edit Enabled on and Toolbar Controls without Save Button, both marked with red boxes
An editable grid without its own Save button.

When you turn on Edit, Page Designer also creates a process named Projects - Save Interactive Grid Data. Step 8 checks it.

Further down on the Attributes tab, set Heading, Fixed To to Region and Fixed Report Height to 220. The project list then scrolls inside its grid, and the two grids below stay in view:

Heading group with Fixed To Region and Fixed Report Height 220 marked with a red box
A fixed height for the grid.

Go back to the Region tab and expand Projects and Columns. Page Designer read the primary key constraint and set up PROJECT_ID by itself: Type is Hidden, and Primary Key and Query Only are on. Leave it as it is:

Column PROJECT_ID with Type Hidden, Query Only and Primary Key on, marked with red boxes
PROJECT_ID is the row key of the grid.

Select PROJECT_NAME and set Heading to Project. Set the Heading of OWNER_NAME to Owner and of DUE_DATE to Due Date.

Step 4: Add the Tasks Grid

Right-click Body again, choose Create Region, and set Name to Tasks, Type to Interactive Grid, Table Name to PLAN_TASKS and Template to Standard. Then set these properties:

  • Layout, Column Span: 6, so the grid takes the left half of the page
  • Master Detail, Master Region: Projects
Tasks region with Layout Column Span 6 and Master Detail Master Region Projects marked with red boxes
Master Region links Tasks to the Projects grid.

On the Attributes tab, make the same three changes as for Projects: Edit Enabled on, Save Button unchecked, and Fixed To Region with Fixed Report Height 220.

Expand the Columns of Tasks and select PROJECT_ID. A new grid shows a foreign key column as a visible Number Field, and Page Designer does not link it to the master grid. Set Type to Hidden, and under Master Detail set Master Column to PROJECT_ID:

Column PROJECT_ID of the Tasks grid with Type Hidden and Master Column PROJECT_ID marked with red boxes
The foreign key column follows the selected project.

Master Column does two things. The grid shows only tasks whose PROJECT_ID matches the selected project, and it fills PROJECT_ID in every new task. Users never type the key.

Select TASK_NAME and set Heading to Task. Then select STATUS, set Type to Select List, and under List of Values set Type to Static Values with three entries: Open / OPEN, In Progress / IN PROGRESS and Done / DONE. Turn Display Null Value off:

Column STATUS with Type Select List and List of Values Static Values Open, In Progress, Done and Display Null Value off, marked with red boxes
STATUS as a select list.

STATUS is NOT NULL, so Value Required is on for this column. Without a column default, a new task starts with an empty status, and Save stops with Status must have some value. The table default never gets a chance. Under Default, set Type to Static and Static Value to OPEN:

Default group of the STATUS column with Type Static and Static Value OPEN marked with a red box
New tasks start with the status Open.

Step 5: Add the Checklist Items Grid

Create a third region with Name Checklist Items, Type Interactive Grid, Table Name PLAN_TASK_ITEMS and Template Standard. Then set:

  • Layout, Start New Row: off, so the grid sits next to Tasks
  • Layout, Column Span: 6
  • Master Detail, Master Region: Tasks
Checklist Items region with Start New Row off, Column Span 6 and Master Region Tasks marked with red boxes
The third grid follows the Tasks grid, not the Projects grid.

The Master Region of this grid is Tasks. That is what makes the page three levels deep: Projects drives Tasks, and Tasks drives Checklist Items. On the Attributes tab, again turn Edit Enabled on, uncheck Save Button, and set the fixed height of 220.

In its Columns, select TASK_ID. Set Type to Hidden and Master Column to TASK_ID:

Column TASK_ID of the Checklist Items grid with Type Hidden and Master Column TASK_ID marked with red boxes
Items follow the selected task.

Set the Heading of ITEM_TEXT to Checklist Item. Then select IS_DONE and set Type to Switch and Heading to Done. Under Settings, turn Use Defaults off and enter On Value Y, On Label Yes, Off Value N and Off Label No. Under Default, set Type to Static and Static Value to N:

Column IS_DONE with Type Switch, Heading Done and Settings On Value Y, On Label Yes, Off Value N, Off Label No marked with red boxes
IS_DONE as a Yes/No switch.

Step 6: Run the Page

Save the page and run it. The Projects grid selects its first row, and the Tasks grid shows the tasks of that project. The Tasks grid also selects its first row, so the Checklist Items grid shows the items of that task:

Running page with Office Move selected in Projects, its two tasks in Tasks and the items of Book movers in Checklist Items
Office Move, its tasks, and the items of Book movers.

Click Set up network in the Tasks grid. The Checklist Items grid refreshes and shows the items of that task:

Set up network selected in the Tasks grid and the Checklist Items grid showing Order switches and Test Wi-Fi coverage, both marked with red boxes
Selecting a task changes the third grid.

Selecting Website Redesign in the Projects grid changed both lower grids: Tasks showed Write content plan, and Checklist Items showed List all current pages.

Step 7: Add One Save Button for All Three Grids

In the Rendering tree, right-click the Projects region and choose Create Button. Set Button Name to SAVE, Label to Save, Slot to Edit and Hot on:

Button SAVE with Label Save, Region Projects, Slot Edit and Hot on marked with red boxes
The Save button in the header of the Projects region.

Keep Behavior, Action at Submit Page:

Behavior group of the SAVE button with Action Submit Page marked with a red box
The button submits the page.

Submitting the page sends the changes of all three grids, and the three save processes store them in one request.

The grid toolbar Save buttons were removed for a reason. In a first test, the page still had them. A new project with a new task saved with the grid Save button failed with ORA-06502: PL/SQL: value or conversion error: character to number conversion error:

Error ORA-06502 character to number conversion error after clicking the Save button of the Tasks grid toolbar, with the error and the button marked with red boxes
The grid toolbar Save failed for a new task with a new item.

The same error came back in every test where a new parent row and its new child rows were saved together with a toolbar Save button. It did not matter which grid's button was clicked. The new task kept its temporary key, t1000, and the database rejected it as a number. Nothing was saved, not even the parent row.

A toolbar Save worked when the parent row already existed, for example a new task under a saved project. The page Save button worked in every case, as the tests in Step 9 show.

Step 8: Check the Order of the Save Processes

Open the Processing tab. Page Designer created one Interactive Grid - Automatic Row Processing (DML) process for each grid when Edit was turned on:

Process Projects - Save Interactive Grid Data with Type Interactive Grid - Automatic Row Processing (DML), Editable Region Projects and Sequence 10 marked with red boxes
The save process of the Projects grid runs first.

The processes must run from the top level down: Projects - Save Interactive Grid Data with Sequence 10, Tasks with 20 and Checklist Items with 30. Page Designer numbers each new process after the existing ones, so creating the grids from the top level down gives this order.

In a test with the Projects process moved to Sequence 40, after the other two, the page Save failed with the same ORA-06502 error and saved nothing. Each parent row must be inserted before its children, so APEX can put the new key into them.

Step 9: Test the Page

First, add all three levels at once. Click Add Row in the Projects grid and enter Team Offsite, owner Maria Lopez, due 11/20/2026. While the new project is selected, the Tasks grid adds an empty row by itself, because Add Row If Empty is on by default. Type Book a venue into it.

The Checklist Items grid also added an empty row for the new task. Type Compare three venues into it, click Add Row in that grid, and type Pay the deposit. All new rows are green, and nothing is saved yet:

New project Team Offsite, new task Book a venue and two new checklist items shown in green, with the Save button marked with a red box
A new project, a new task and two new items before saving.

Click Save. The page reloads. Select Team Offsite to see the saved rows:

Team Offsite selected after saving, with the task Book a venue and its two checklist items
All three levels were saved in one submit.

Check the keys in SQL:

Example:

select p.project_id, p.project_name, t.task_id, t.project_id as task_project_id, t.task_name, i.item_id, i.task_id as item_task_id, i.item_text
from plan_projects p
join plan_tasks t on t.project_id = p.project_id
join plan_task_items i on i.task_id = t.task_id
where p.project_name = 'Team Offsite'
order by i.item_id;

Output:

   PROJECT_ID PROJECT_NAME       TASK_ID    TASK_PROJECT_ID TASK_NAME          ITEM_ID    ITEM_TASK_ID ITEM_TEXT
_____________ _______________ __________ __________________ _______________ __________ _______________ _______________________
           81 Team Offsite            61                 81 Book a venue            61              61 Compare three venues
           81 Team Offsite            61                 81 Book a venue            62              61 Pay the deposit

The task got the new PROJECT_ID 81, and both items got the new TASK_ID 61. The browser only knew temporary keys when the user typed the rows; APEX filled in the real keys during the save.

Second, unsaved rows under different parents. In this test, a new task Label the boxes was added under Office Move. Then Website Redesign was selected, and a new task Pick a color scheme was added there.

One click on Save stored both tasks, each with its own PROJECT_ID. The grid keeps the pending changes of every parent until the page is submitted.

Third, a missing value. Select Team Offsite, click Add Row in the Checklist Items grid, leave the new row empty, and click Save:

Error message Checklist Item must have some value and the empty new item cell marked as an error
An empty checklist item blocks the save.

The message Checklist Item must have some value appeared, the empty cell was marked, and the page was not submitted.

The empty rows that a grid adds on its own are different. In an earlier test, a new project was saved while the Tasks and Checklist Items grids still held their untouched empty rows. The project was saved, and the empty rows were ignored.

Fourth, delete a project with its children. Select Team Offsite, open the row actions menu, and choose Delete Row:

Row actions menu of the Team Offsite row with Delete Row marked with a red box
Delete Row in the row actions menu.

The row is struck through, and the lower grids are empty. Click Save:

Team Offsite struck through as deleted, marked with a red box, with empty Tasks and Checklist Items grids
The project is marked for deletion until the page is saved.

Check that the project, its task and its items are gone:

Example:

select (select count(*) from plan_projects where project_name = 'Team Offsite') as projects,
       (select count(*) from plan_tasks where task_name = 'Book a venue') as tasks,
       (select count(*) from plan_task_items where item_text in ('Compare three venues', 'Pay the deposit')) as items
from dual;

Output:

   PROJECTS    TASKS    ITEMS
___________ ________ ________
          0        0        0

The page deleted only the project row. The foreign keys with ON DELETE CASCADE removed the task and the two items.

Things to Know

  • Any number of levels works the same way. Each grid's Master Region points to the grid one level up, and its foreign key column has Master Column set to the parent's key.
  • Page Designer finds the primary key of the table, but it does not link the foreign key column. Set Master Column yourself, or the lower grid shows all rows.
  • Use one page Save button. With the grid toolbar Save buttons, saving a new parent together with its new children failed with ORA-06502, and nothing was saved.
  • Keep the save processes in level order: parent first, then child, then grandchild.
  • The save is all or nothing. When one row fails, no row of any grid is saved.
  • Duplicate names are stopped by the unique keys. A second project named Team Offsite failed with ORA-00001 on PLAN_PROJECTS_NAME_UK. Add a validation if users need a friendlier message.
  • Give NOT NULL columns with table defaults a column Default too, such as STATUS and IS_DONE here. Without it, a new task failed with Status must have some value, because Value Required is checked before the row reaches the table.

Related Guides

Conclusion

A three-level master-detail page in Oracle APEX is three editable Interactive Grids chained with Master Region and Master Column. The second grid follows the first, and the third follows the second, so users can move through projects, tasks and checklist items on one page.

Saving is where the details matter. With the save processes in level order and one page Save button, a new project, its task and its items were stored in a single submit with the correct keys. A delete on the top level removed everything below it through the foreign keys.

Get more Vinish.dev tutorials on Google

See my articles more often when you search Google.

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