Interactive Grid Bulk Actions with AJAX Callbacks in Oracle APEX (JSON Edition)

Intro
Interactive Grids in Oracle APEX are powerful for editing rows, but what if you need to apply bulk actions — like updating or deleting multiple rows at once?
This post walks through how I built bulk update and bulk delete features for an IG using apex.server.process, JSON payloads, and proper error handling. Along the way, I discovered some important concepts that every APEX developer should understand.
Step 1: Enable Row Selection in IG
Go to your Interactive Grid’s attributes.
Enable Row Selection → this adds the leftmost checkbox column.
Now users can tick multiple rows, Gmail-style.
(Screenshot: IG with row checkboxes)
Step 2: Collecting Selected Rows in JavaScript
Use IG’s API to get the selected records and pull primary keys (e.g., EMPNO
):
var ig$ = apex.region("emp").widget();
var model = ig$.interactiveGrid("getViews","grid").model;
var sel = ig$.interactiveGrid("getSelectedRecords");
var ids = sel.map(function(rec){
return model.getValue(rec, "EMPNO"); // primary key
});
(Screenshot: console log of IDs when rows are selected)
Step 3: Sending Data to the Server (JSON Payload)
Instead of old CSV tricks, send structured JSON.
var payload = JSON.stringify({ ids: ids, setTo: 0, reason: "bulk clear" });
apex.server.process(
"BULK_CLEAR_COMM_JSON", // Ajax Callback name
{ x01: payload }, // JSON string → g_x01
{
success: function(pData){
if (pData && pData.ok === false) {
apex.message.showErrors(pData.errors);
return;
}
var n = (pData && pData.updated) ? pData.updated : 0;
apex.message.showPageSuccess("Updated COMM for " + n + " row(s).");
apex.region("emp").refresh();
},
error: function(req, status, err){
apex.message.showErrors([{ type:'error', location:'page', message:'Server error: ' + err }]);
}
}
);
Step 4: The PL/SQL Ajax Callback
On the server, parse JSON, loop IDs, perform the update, and return a JSON result:
declare
j apex_json.t_values;
l_count number := 0;
l_id number;
l_size pls_integer;
l_setto number;
l_reason varchar2(4000);
procedure send_error(p_msg varchar2) is
begin
apex_json.open_object;
apex_json.write('ok', false);
apex_json.open_array('errors');
apex_json.open_object;
apex_json.write('type','error');
apex_json.write('location','page');
apex_json.write('message', p_msg);
apex_json.close_object;
apex_json.close_array;
apex_json.close_object;
end;
begin
-- Parse JSON {"ids":[..], "setTo":0, "reason":"..."}
apex_json.parse(j, apex_application.g_x01);
l_setto := apex_json.get_number(j,'setTo');
l_reason := apex_json.get_varchar2(j,'reason');
l_size := apex_json.get_count(j,'ids');
if l_size = 0 then
send_error('No rows selected.'); return;
end if;
for i in 1..l_size loop
l_id := apex_json.get_number(j,'ids[%d]', i);
update emp_copy set comm = l_setto where empno = l_id;
l_count := l_count + sql%rowcount;
end loop;
apex_json.open_object;
apex_json.write('ok', true);
apex_json.write('updated', l_count);
apex_json.close_object;
end;
(Screenshot: IG before & after COMM column cleared)
BEFORE
AFTER
Step 5: Bulk Delete Example
Same pattern, but PL/SQL performs a delete
:
JavaScript:
apex.message.confirm(
"Delete " + ids.length + " row(s)?",
function(ok){
if(!ok) return;
apex.server.process("BULK_DELETE_EMP",
{ x01: JSON.stringify({ ids: ids }) },
{ success: function(pData){ ... } }
);
}
);
PL/SQL:
for i in 1..l_size loop
l_id := apex_json.get_number(j, 'ids[%d]', i);
delete from emp_copy where empno = l_id;
l_count := l_count + sql%rowcount;
end loop;
apex_json.open_object;
apex_json.write('ok', true);
apex_json.write('deleted', l_count);
apex_json.close_object;
(Screenshot: rows disappearing from IG after delete)
BEFORE
AFTER
Step 6: Error Handling the Right Way
One big lesson: AJAX “success” callback fires even on business errors because HTTP 200 means protocol success.
Business/validation errors → handle inside PL/SQL with
ok:false
JSON.Client inspects
if (pData.ok===false)
→ show errors withapex.message.showErrors
.Only network/500 errors trigger the
error
callback.
(Diagram: HTTP vs App error flow)
Step 7: Real-World Extensions
Audit logging → log who updated/deleted, old/new values, and reason.
Soft deletes → mark
is_deleted='Y'
instead of hard delete.UX polish → disable button while Ajax in progress.
Inline errors → tie errors to specific items (
pageItem: 'P1_SETTO'
).
Key Takeaways
Bulk IG actions = row selectors + apex.server.process + JSON payloads.
Always return structured JSON (
ok:true/false
,errors[]
).Use
apex.message.showErrors
for clean UX.Separate HTTP failures (500, network) from business errors.
Closing
Building bulk update/delete in APEX IGs shows how client-side JS and server-side PL/SQL work hand in hand.
👉 Next up, I’ll share a focused post on why apex.server.process success
fires even when there’s an error — and how to handle it with proper error JSON.
#OracleAPEX
#LowCode
#PLSQL
#WebDev
#SQL
Subscribe to my newsletter
Read articles from Bhuvnesh Singh Chauhan (bhuvi_champ) directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
