How to Make a Field Required Only When Another Field Has a Value in Oracle APEX

Toggle the required marker and attribute with a dynamic action, repeat the rule in a conditional validation and protect the table with a check constraint.

Some fields are required only in certain cases. A discount needs a reason, but a full-price entry does not. The Value Required switch of a page item is always on or always off, so it cannot express "required when". If you leave it off and check nothing, users save discounts with no explanation.

This guide makes Discount Reason required only when Discount % is more than 0. The browser shows the required marker and blocks the save at once, a server-side validation repeats the rule, and a check constraint protects the table. The guide uses price quotes as the example; the same steps work for any table.

The older post How to Show or Hide Fields Based on Another Item's Value in Oracle APEX shows and hides fields. Here the field stays visible, and only its required state changes with the value of another item.

Step 1: Create the Tables

The example needs two tables. CLIENT_ACCOUNTS lists the clients. PRICE_QUOTES stores one quote per row, with DISCOUNT_PCT and an optional DISCOUNT_REASON.

The rule itself is part of the table: the check constraint PRICE_QUOTES_REASON_CK allows an empty reason only when the discount is 0. Create the tables, the index and the sample data:

Example:

create table client_accounts (
  client_id     number generated by default on null as identity,
  client_name   varchar2(100 char) not null,
  constraint client_accounts_pk primary key (client_id),
  constraint client_accounts_name_uk unique (client_name)
);

create table price_quotes (
  quote_id        number generated by default on null as identity,
  client_id       number             not null,
  quote_date      date               default on null sysdate not null,
  amount          number(12,2)       not null,
  discount_pct    number(4,1)        default on null 0 not null,
  discount_reason varchar2(200 char),
  created_by      varchar2(255)      default on null
                    coalesce(sys_context('APEX$SESSION', 'APP_USER'), user) not null,
  constraint price_quotes_pk primary key (quote_id),
  constraint price_quotes_client_fk foreign key (client_id)
    references client_accounts (client_id),
  constraint price_quotes_amount_ck check (amount > 0),
  constraint price_quotes_discount_ck check (discount_pct between 0 and 50),
  constraint price_quotes_reason_ck check (discount_pct = 0 or discount_reason is not null)
);

create index price_quotes_client_ix on price_quotes (client_id);

insert into client_accounts (client_name) values ('Blue Harbor Ltd');
insert into client_accounts (client_name) values ('Cedar Point Labs');
insert into client_accounts (client_name) values ('Northwind Traders');

insert into price_quotes (client_id, quote_date, amount, discount_pct, discount_reason, created_by)
values (1, date '2026-10-01', 12000, 0, null, 'DEMO_DEV');
insert into price_quotes (client_id, quote_date, amount, discount_pct, discount_reason, created_by)
values (2, date '2026-10-05', 8500, 10, 'Two-year contract', 'SALES_MANAGER');
insert into price_quotes (client_id, quote_date, amount, discount_pct, discount_reason, created_by)
values (3, date '2026-10-09', 4300, 0, null, 'DEMO_DEV');
commit;

select q.quote_id, c.client_name, q.amount, q.discount_pct, q.discount_reason
from   price_quotes q
join   client_accounts c on c.client_id = q.client_id
order  by q.quote_id;

Output:

Table CLIENT_ACCOUNTS created.

Table PRICE_QUOTES created.

Index PRICE_QUOTES_CLIENT_IX created.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

Commit complete.

   QUOTE_ID CLIENT_NAME             AMOUNT    DISCOUNT_PCT DISCOUNT_REASON
___________ ____________________ _________ _______________ ____________________
          1 Blue Harbor Ltd          12000               0
          2 Cedar Point Labs          8500              10 Two-year contract
          3 Northwind Traders         4300               0
  • PRICE_QUOTES_CLIENT_FK links each quote to a client, and PRICE_QUOTES_CLIENT_IX indexes that foreign key.
  • PRICE_QUOTES_DISCOUNT_CK allows discounts from 0 to 50, and PRICE_QUOTES_AMOUNT_CK only positive amounts.
  • DISCOUNT_PCT, QUOTE_DATE and CREATED_BY use DEFAULT ON NULL, so the table fills them when a value is missing. No trigger is needed.

Step 2: Create the Report and Form Pages

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

Create a Page dialog with the Interactive Report page type marked with a red box
The Interactive Report page type.

Enter Page Number 440 and Name Price Quotes. Turn on Include Form Page, enter Form Page Number 441 and Form Page Name Quote, and keep Form Page Mode Modal Dialog:

Create Interactive Report wizard with page 440 Price Quotes and form page 441 Quote marked with red boxes
One wizard creates the report page and the form page.

Under Data Source, keep Local Database and Source Type Table, and enter Table / View Name PRICE_QUOTES:

Data Source section with Table / View Name PRICE_QUOTES marked with a red box
The form is based on the PRICE_QUOTES table.

Keep Use Breadcrumb and Use Navigation on, so the report page gets a menu entry, and click Next:

Navigation section of the Create Interactive Report wizard with breadcrumb and navigation menu settings
The Navigation settings of the wizard.

On the Primary Key screen, select QUOTE_ID (Number) as Primary Key Column 1 and click Create Page:

Primary Key screen of the wizard with QUOTE_ID (Number) selected and marked with a red box
QUOTE_ID identifies each row.

APEX creates the Price Quotes report on page 440 and the Quote modal form on page 441. The wizard turned on Value Required for the NOT NULL columns. DISCOUNT_REASON is nullable, so P441_DISCOUNT_REASON got Value Required off and the template Optional - Floating.

Step 3: Remove the Audit Item

The wizard also created P441_CREATED_BY. The table fills this column with DEFAULT ON NULL, so users should not type it. Open page 441 in Page Designer, right-click P441_CREATED_BY in the Rendering tree and choose Delete:

Page Designer context menu of P441_CREATED_BY with Delete marked with a red box
Delete the item for a column that the table fills itself.

Click Save.

Step 4: Set Up the Discount Item

Select P441_DISCOUNT_PCT. Change Label to Discount %, and in the Settings group enter Minimum Value 0 and Maximum Value 50, the same range as the check constraint:

P441_DISCOUNT_PCT with Label Discount %, Minimum Value 0 and Maximum Value 50 marked with red boxes
The discount item and its allowed range.

Scroll to the Default group. Set Type Static and Static Value 0, so a new quote starts without a discount:

Default group of P441_DISCOUNT_PCT with Type Static and Static Value 0 marked with a red box
New quotes start with Discount % 0.

Step 5: Prepare the Reason Item

Select P441_DISCOUNT_REASON. In the Appearance group, change Template to Required - Floating. Leave Validation > Value Required off:

P441_DISCOUNT_REASON with Template Required - Floating and Value Required off marked with red boxes
The Required template, with Value Required still off.

The two settings do different jobs. The template only adds the markup for the required marker, the small red corner of the field. Value Required would make the field required in every case, which is not the rule here.

In a first test, the dynamic action of the next step ran with the Optional - Floating template. The field became required, but no marker appeared, because that template has no marker element.

In the Help group, enter Inline Help Text Required when the discount is more than 0. The rule is then visible below the field:

Help group of P441_DISCOUNT_REASON with Inline Help Text marked with a red box
Inline help text explains when the reason is needed.

Click Save.

Step 6: Toggle the Required State in the Browser

Right-click P441_DISCOUNT_PCT in the Rendering tree and choose Create Dynamic Action:

Context menu of P441_DISCOUNT_PCT with Create Dynamic Action marked with a red box
Create the dynamic action on the controlling item.

Set Name Toggle Reason Required. Event Change, Selection Type Item(s) and Item(s) P441_DISCOUNT_PCT are already filled in:

Dynamic action Toggle Reason Required with Name, Event Change, Selection Type and Item(s) marked with red boxes
The dynamic action fires when the discount changes.

Select the Show action under True. Change Action to Execute JavaScript Code and enter this code:

Example:

var needed = Number(apex.item('P441_DISCOUNT_PCT').getValue()) > 0;
$('#P441_DISCOUNT_REASON').prop('required', needed);
$('#P441_DISCOUNT_REASON_CONTAINER').toggleClass('is-required', needed);

Turn on Fire on Initialization and click Save:

Execute JavaScript Code action with Action, Code and Fire on Initialization marked with red boxes
One action handles both directions.

The first line checks whether the discount is more than 0. The second line sets the HTML required attribute of the input, which the browser checks when the page is submitted. The third line adds or removes the is-required class on P441_DISCOUNT_REASON_CONTAINER, the wrapper that APEX renders around the item. That class shows the red marker.

No client-side condition and no False action are needed, because the code passes true or false itself. Fire on Initialization runs the code when the dialog opens. A new quote with 0 loses the marker that the template added, and an existing discounted quote gets it at once.

Step 7: Add the Server-Side Validation

The browser check helps users, but anything in the browser can be bypassed. A validation repeats the rule on the server. Right-click P441_DISCOUNT_REASON and choose Create Validation:

Context menu of P441_DISCOUNT_REASON with Create Validation marked with a red box
Create the validation from the item it checks.

Set Name Discount Reason Required. Change Validation > Type to Item is NOT NULL and enter Item P441_DISCOUNT_REASON. Enter Error Message Enter a reason for the discount. Associated Item is already P441_DISCOUNT_REASON.

Then set Server-side Condition > Type to Expression, keep Language PL/SQL and enter this PL/SQL Expression:

Example:

to_number(:P441_DISCOUNT_PCT) > 0
Validation Discount Reason Required with Name, Type, Item, Error Message and the Server-side Condition expression marked with red boxes
The validation runs only when the discount is more than 0.

The condition decides whether the validation runs at all. With Discount % 0 it is skipped, and an empty reason is fine. Page item values are strings in session state, so TO_NUMBER makes the comparison numeric. Click Save.

Step 8: Test the Rule

Run page 440. The report lists the three sample quotes:

Price Quotes interactive report with three quotes, one with a 10 percent discount and the reason Two-year contract
The Price Quotes report.

A Quote Without a Discount

Click Create. Discount % starts at 0, and Discount Reason has no required marker:

New Quote dialog with Discount % 0 and Discount Reason without a required marker, both marked with a red box
With no discount, the reason is optional.

Select Blue Harbor Ltd, enter a Quote Date and Amount 2400, keep Discount % 0 and click Create. The dialog closed and the quote was saved without a reason.

A Discount Without a Reason

Click Create again. Select Cedar Point Labs, enter a Quote Date and Amount 6000, then type 10 in Discount %. When the field loses focus, the red marker appears on Discount Reason:

Quote dialog with Discount % 10 and the red required marker on Discount Reason, marked with a red box
The reason became required as soon as the discount changed.

Leave the reason empty and click Create. The browser stops the submit:

Quote dialog with the error Discount Reason must have some value. below the field and in the notification, marked with a red box
The required check runs in the browser first.

The message Discount Reason must have some value. is the standard APEX text for a required item. In the test, no request was sent to the server. Typing 10 and clicking Create directly also worked, because the Change event fires when the field loses focus, before the click.

A Forced Submit (Negative Test)

To check the server side, the same form was submitted from the browser console with the client-side checks turned off:

Example:

apex.page.submit({ request: 'CREATE', validate: false });

This time the request reached the server, and the validation stopped it:

Quote dialog with the server error Enter a reason for the discount. below Discount Reason, marked with a red box
The server-side validation catches a bypassed browser check.

The message is now the validation's own text, Enter a reason for the discount. No row was inserted. After the page reloaded with the error, the marker was still shown, because Fire on Initialization ran again for Discount % 10.

A Discount With a Reason

Enter Discount Reason Volume order of 200 units and click Create. The dialog closes and the report shows the new quote:

Price Quotes report with the new Cedar Point Labs quote with 10 percent discount and reason Volume order of 200 units
A discount with a reason saves normally.

Editing an Existing Quote

Click the edit icon of the Cedar Point Labs quote with the reason Two-year contract. The marker is there as soon as the dialog opens:

Edit dialog of a quote with Discount % 10, reason Two-year contract and the required marker, marked with a red box
Fire on Initialization sets the state for stored values.

Changing Discount % to 0 removed the marker and the required attribute, and 12.5 or 0.5 turned them on again. The table confirms what was saved:

Example:

select q.quote_id, c.client_name, q.amount, q.discount_pct, q.discount_reason, q.created_by
from   price_quotes q
join   client_accounts c on c.client_id = q.client_id
order  by q.quote_id;

Output:

   QUOTE_ID CLIENT_NAME             AMOUNT    DISCOUNT_PCT DISCOUNT_REASON              CREATED_BY
___________ ____________________ _________ _______________ ____________________________ ________________
          1 Blue Harbor Ltd          12000               0                              DEMO_DEV
          2 Cedar Point Labs          8500              10 Two-year contract            SALES_MANAGER
          3 Northwind Traders         4300               0                              DEMO_DEV
         21 Blue Harbor Ltd           2400               0                              DEMO_DEV
         22 Cedar Point Labs          6000              10 Volume order of 200 units    DEMO_DEV

The two new rows are quotes 21 and 22. The quote without a discount has no reason, and the discounted one has its reason.

The Constraint as the Last Guard

Other paths into the table, such as a script or a REST service, skip the page. The check constraint still applies to them:

Example:

insert into price_quotes (client_id, amount, discount_pct, discount_reason)
values (3, 1500, 5, null);

insert into price_quotes (client_id, amount, discount_pct, discount_reason)
values (3, 1500, 5, 'Early payment');

rollback;

Output:

Error starting at line : 1 File @ b47/11-constraint.sql
In command -
insert into price_quotes (client_id, amount, discount_pct, discount_reason)
values (3, 1500, 5, null)
Error report -
ORA-02290: check constraint (APXSOL.PRICE_QUOTES_REASON_CK) violated
Help: https://docs.oracle.com/error-help/db/ora-02290/

1 row inserted.

Rollback complete.

The insert without a reason failed with ORA-02290, and the same insert with a reason worked. The ROLLBACK removed the test row.

Things to Know

  • Use the Required template, not Value Required, for a field that is required only sometimes. Value Required makes the field required in every case.
  • The is-required class works with the item's container, which APEX names ITEM_CONTAINER, for example P441_DISCOUNT_REASON_CONTAINER.
  • The Minimum Value and Maximum Value of a Number Field are checked in the browser too. In the test, Discount % 60 showed Discount % must be a number between 0 and 50. without a request.
  • The browser check never replaces the validation. The forced submit in the test reached the server, and only the validation stopped it.

Related Guides

Conclusion

A conditionally required field in Oracle APEX needs the rule in three places. A dynamic action with Fire on Initialization turns the required attribute and the marker on and off in the browser. A validation with a server-side condition repeats the rule for every submit.

A check constraint keeps the data right for every other path into the table. Give the field the Required template with Value Required off, so the marker can appear only when the rule applies, and add inline help text so users know why.

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