How to Validate Email and Phone Formats with Regular Expressions in Oracle APEX

Add regular expression validations, E-Mail and Phone Number subtypes, and matching REGEXP_LIKE check constraints, then test valid and invalid values.

A form that accepts any text in an email or phone field soon fills the table with values like "john@company" or "555 0100". Emails bounce, and nobody can call the number. The best time to stop a bad value is when the user types it, with a clear message next to the field.

Oracle APEX has a validation type for this: Item matches Regular Expression. This guide uses it for an email and a phone field, adds the HTML5 email and phone input types, and backs both checks with REGEXP_LIKE check constraints in the database, so even data loaded outside the form must follow the same format.

The guide uses supplier contacts as the example; the same steps work for any table. The post Oracle Apex Validation: PL/SQL Function Returning Error Text shows a general validation written in PL/SQL. This one needs no code: it is only about format checks with regular expressions.

Step 1: Create the Tables

The example needs two tables. SUPPLIERS holds the companies, and SUPPLIER_CONTACTS holds one row per contact person with an email and a phone number. EMAIL is required; PHONE is optional.

Both format rules are check constraints with REGEXP_LIKE. Create the tables, the indexes and the sample data:

Example:

create table suppliers (
  supplier_id    number generated by default on null as identity,
  supplier_name  varchar2(100) not null,
  constraint suppliers_pk primary key (supplier_id),
  constraint suppliers_name_uk unique (supplier_name)
);

create table supplier_contacts (
  contact_id   number generated by default on null as identity,
  supplier_id  number        not null,
  full_name    varchar2(100) not null,
  email        varchar2(254) not null,
  phone        varchar2(25),
  created_on   date          default on null sysdate not null,
  constraint supplier_contacts_pk primary key (contact_id),
  constraint supplier_contacts_supplier_fk foreign key (supplier_id)
    references suppliers (supplier_id),
  constraint supplier_contacts_email_ck check (
    regexp_like(email, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$')),
  constraint supplier_contacts_phone_ck check (
    regexp_like(phone, '^\+[1-9]([ -]?[0-9]){7,14}$'))
);

create unique index supplier_contacts_email_uk on supplier_contacts (lower(email));
create index supplier_contacts_supplier_ix on supplier_contacts (supplier_id);

insert into suppliers (supplier_name) values ('Northwind Traders');
insert into suppliers (supplier_name) values ('Blue Harbor Supplies');
insert into suppliers (supplier_name) values ('Alpine Components');

insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Laura Chen', 'laura.chen@northwind.example.com', '+1 415 555 0142');
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Mark Weber', 'm.weber@northwind.example.com', '+49 30 1234567');
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (2, 'Priya Nair', 'priya.nair@blueharbor.example.in', '+91 98765 43210');
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (2, 'Tom Fisher', 'tom+orders@blueharbor.example.in', null);
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (3, 'Anna Rossi', 'anna.rossi@alpine.example.it', '+39-02-555-0199');
commit;

select c.contact_id, s.supplier_name, c.full_name, c.email, c.phone
from   supplier_contacts c join suppliers s on s.supplier_id = c.supplier_id
order  by c.contact_id;

Output:

Table SUPPLIERS created.

Table SUPPLIER_CONTACTS created.

Index SUPPLIER_CONTACTS_EMAIL_UK created.

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

Commit complete.

   CONTACT_ID SUPPLIER_NAME           FULL_NAME     EMAIL                               PHONE
_____________ _______________________ _____________ ___________________________________ __________________
            1 Northwind Traders       Laura Chen    laura.chen@northwind.example.com    +1 415 555 0142
            2 Northwind Traders       Mark Weber    m.weber@northwind.example.com       +49 30 1234567
            3 Blue Harbor Supplies    Priya Nair    priya.nair@blueharbor.example.in    +91 98765 43210
            4 Blue Harbor Supplies    Tom Fisher    tom+orders@blueharbor.example.in
            5 Alpine Components       Anna Rossi    anna.rossi@alpine.example.it        +39-02-555-0199
  • SUPPLIER_CONTACTS_EMAIL_CK accepts a name part of letters, digits and . _ % + -, then @, then a domain with at least one dot and a top-level part of two or more letters.
  • SUPPLIER_CONTACTS_PHONE_CK accepts a + sign, then 8 to 15 digits in total (the international E.164 length). A single space or dash may sit between digits.
  • A check constraint passes when the value is NULL, so a contact without a phone number is allowed.
  • SUPPLIER_CONTACTS_EMAIL_UK is a unique function-based index on LOWER(EMAIL). It stops the same address from being stored twice in a different case.
  • CREATED_ON gets SYSDATE from DEFAULT ON NULL, so neither the form nor a trigger has to fill it.

Step 2: Test the Patterns in SQL

Before you put a pattern into a form, test it with a list of good and bad values. This query runs both patterns against sample strings:

Example:

with emails (val) as (
  select 'laura.chen@northwind.example.com' from dual union all
  select 'tom+orders@blueharbor.example.in' from dual union all
  select 'laura.chen@northwind'             from dual union all
  select 'laura chen@northwind.com'         from dual union all
  select 'laura@@northwind.com'             from dual union all
  select '@northwind.com'                   from dual
)
select val as email,
       case when regexp_like(val, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$')
            then 'valid' else 'invalid' end as result
from   emails;

with phones (val) as (
  select '+1 415 555 0142'  from dual union all
  select '+39-02-555-0199'  from dual union all
  select '+919876543210'    from dual union all
  select '415 555 0142'     from dual union all
  select '+1 (415) 555-0142' from dual union all
  select '+1 415 555'       from dual union all
  select '+0 415 555 0142'  from dual
)
select val as phone,
       case when regexp_like(val, '^\+[1-9]([ -]?[0-9]){7,14}$')
            then 'valid' else 'invalid' end as result
from   phones;

Output:

EMAIL                               RESULT
___________________________________ __________
laura.chen@northwind.example.com    valid
tom+orders@blueharbor.example.in    valid
laura.chen@northwind                invalid
laura chen@northwind.com            invalid
laura@@northwind.com                invalid
@northwind.com                      invalid

6 rows selected.

PHONE                RESULT
____________________ __________
+1 415 555 0142      valid
+39-02-555-0199      valid
+919876543210        valid
415 555 0142         invalid
+1 (415) 555-0142    invalid
+1 415 555           invalid
+0 415 555 0142      invalid

7 rows selected.

The email pattern rejects a missing top-level domain, a space, a double @ and an empty name part. The phone pattern rejects a number without +, brackets, a number that is too short, and a country code starting with 0.

Now try to insert bad rows directly in SQL. The constraints reject them, and the unique index rejects an existing email typed in a different case:

Example:

insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Bad Email', 'laura.chen@northwind', '+1 415 555 0100');

insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Bad Phone', 'bad.phone@northwind.example.com', '415-555-0100');

insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Same Email', 'Laura.Chen@Northwind.Example.com', null);

select count(*) as contacts from supplier_contacts;

Output:

Error starting at line : 1 File @ b42/11-bad-rows.sql
In command -
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Bad Email', 'laura.chen@northwind', '+1 415 555 0100')
Error report -
ORA-02290: check constraint (APXSOL.SUPPLIER_CONTACTS_EMAIL_CK) violated
Help: https://docs.oracle.com/error-help/db/ora-02290/

Error starting at line : 4 File @ b42/11-bad-rows.sql
In command -
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Bad Phone', 'bad.phone@northwind.example.com', '415-555-0100')
Error report -
ORA-02290: check constraint (APXSOL.SUPPLIER_CONTACTS_PHONE_CK) violated
Help: https://docs.oracle.com/error-help/db/ora-02290/

Error starting at line : 7 File @ b42/11-bad-rows.sql
In command -
insert into supplier_contacts (supplier_id, full_name, email, phone)
values (1, 'Same Email', 'Laura.Chen@Northwind.Example.com', null)
Error report -
ORA-00001: unique constraint (APXSOL.SUPPLIER_CONTACTS_EMAIL_UK) violated on
table APXSOL.SUPPLIER_CONTACTS columns (SYS_NC00007$)
ORA-03301: (ORA-00001 details) row with column values
(SYS_NC00007$:'laura.chen@northwind.example.com') already exists
Help: https://docs.oracle.com/error-help/db/ora-00001/

   CONTACTS
___________
          5

The table still has its five rows.

Two details of Oracle regular expressions matter for the patterns. Oracle does not read \d as a digit inside brackets, and a pattern without the ^ and $ anchors matches any value that only contains a valid part:

Example:

select case when regexp_like('+1 415 555 0142', '^\+[\d ]+$')
            then 'match' else 'no match' end as d_in_brackets,
       case when regexp_like('+1 415 555 0142', '^\+[0-9 ]+$')
            then 'match' else 'no match' end as zero_to_nine_in_brackets
from   dual;

select case when regexp_like('call me: ann@shop.example.com',
                             '[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}')
            then 'match' else 'no match' end as without_anchors,
       case when regexp_like('call me: ann@shop.example.com',
                             '^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$')
            then 'match' else 'no match' end as with_anchors
from   dual;

Output:

D_IN_BRACKETS    ZERO_TO_NINE_IN_BRACKETS
________________ ___________________________
no match         match

WITHOUT_ANCHORS    WITH_ANCHORS
__________________ _______________
match              no match

So use [0-9] or [[:digit:]] inside brackets, and always anchor the whole pattern. The database is now the last line of defense. The next steps add friendly checks in the form, so users never see these ORA errors for a wrong format.

Step 3: 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 390 and Name Supplier Contacts. Turn on Include Form Page, enter Form Page Number 391 and Form Page Name Supplier Contact, and set Form Page Mode to Modal Dialog:

Create Interactive Report wizard with Page Number 390, Name Supplier Contacts and the form page settings marked with red boxes
A report page and a modal form page.

Scroll down to Data Source. Keep Source Type Table and enter SUPPLIER_CONTACTS as the Table / View Name. Keep the Navigation settings, so the report gets a menu entry, and click Next:

Data Source section of the wizard with Table / View Name SUPPLIER_CONTACTS marked with a red box
The contacts table as the source.

The wizard reads the primary key of the table and fills Primary Key Column 1 with CONTACT_ID. Click Create Page:

Primary Key screen of the wizard with Primary Key Column 1 CONTACT_ID marked with a red box
CONTACT_ID is the primary key.

The wizard creates page 390 with the report and page 391 with the form. It also reads the SUPPLIER_ID foreign key. It creates a shared list of values named SUPPLIERS.SUPPLIER_NAME, makes P391_SUPPLIER_ID a select list on it, and shows supplier names in the report.

Step 4: Tidy Up the Form Items

Open page 391 in Page Designer. Select P391_SUPPLIER_ID and, under List of Values, set Null Display Value to "- Select Supplier -", so an empty select list asks for a choice:

Page Designer with P391_SUPPLIER_ID and Null Display Value - Select Supplier - marked with a red box
The supplier select list from the wizard.

The wizard also added P391_CREATED_ON as a required date picker. The database fills that column itself, so right-click P391_CREATED_ON and choose Delete. DEFAULT ON NULL then stamps the date when a new contact is saved.

Step 5: Use the Email and Phone Input Types

Select P391_EMAIL. Under Settings, set Subtype to E-Mail. Under Appearance, set Value Placeholder to name@company.com:

Page Designer with P391_EMAIL, Subtype E-Mail and Value Placeholder name@company.com marked with red boxes
The email field becomes an HTML5 email input.

Subtype E-Mail renders the field as input type="email", so the browser runs its own email check before the page is submitted. Step 8 shows what that check catches and what it lets through.

Select P391_PHONE. Set Subtype to Phone Number and Value Placeholder to +1 415 555 0142:

Page Designer with P391_PHONE, Subtype Phone Number and Value Placeholder +1 415 555 0142 marked with red boxes
The phone field becomes a tel input.

Subtype Phone Number renders input type="tel". The browser does not check the format of a tel field: in Step 8 it let 415 555 0100 through. The regular expression in Step 7 does that check.

Scroll down to Help and enter this Inline Help Text: "Start with + and the country code. Spaces and dashes are allowed.":

Help group of P391_PHONE with the Inline Help Text marked with a red box
The rule shown below the phone field.

Step 6: Validate the Email Format

Open the Processing tab. Right-click Validating and choose Create Validation. Set Name to Email format and Type to Item matches Regular Expression. Select P391_EMAIL as the Item and enter this Regular Expression:

Example:

^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$
Page Designer with the Email format validation, Name, Type Item matches Regular Expression, Item P391_EMAIL and Regular Expression marked with red boxes
The email validation.

It is the same pattern as SUPPLIER_CONTACTS_EMAIL_CK, so the form and the table follow one rule. The ^ and $ anchors make the whole value match, not just a part of it.

Under Error, enter the Error Message "Enter a valid email address, for example name@company.com." and set Associated Item to P391_EMAIL. Keep Display Location Inline with Field and in Notification:

Error group of the Email format validation with the Error Message and Associated Item P391_EMAIL marked with red boxes
The message and the field it belongs to.

Step 7: Validate the Phone Format

Right-click Validating again and choose Create Validation. Set Name to Phone format, Type to Item matches Regular Expression, Item to P391_PHONE, and enter this Regular Expression:

Example:

^\+[1-9]([ -]?[0-9]){7,14}$
Page Designer with the Phone format validation, Type Item matches Regular Expression, Item P391_PHONE and the Regular Expression
The phone validation.

Read the pattern from left to right: a + sign, one digit from 1 to 9, then 7 to 14 more digits, each with an optional space or dash before it.

Under Error, enter the Error Message "Enter the phone number with + and the country code, for example +1 415 555 0142." and set Associated Item to P391_PHONE:

Error group of the Phone format validation with the Error Message and Associated Item P391_PHONE marked with red boxes
The phone error message.

Save the page. The Delete button that the wizard created has Execute Validations turned off, so these checks never block a delete.

Step 8: Test the Form

Run page 390. The report lists the five sample contacts:

Supplier Contacts interactive report with five contacts and a Create button
The sample contacts.

Click Create. The email and phone fields show their placeholders, and the help text sits below the phone field:

Supplier Contact dialog with the Supplier select list, Full Name, the Email placeholder name@company.com and the Phone placeholder and help text
The empty form.

Select Northwind Traders, enter Sara Klein, the email sara.klein@northwind and the phone 415 555 0100, and click Create. Both validations fail. APEX lists the errors in the notification and shows each message below its field:

Supplier Contact dialog with the notification 2 errors have occurred and the email and phone error messages below the fields
The server-side regular expression checks.

The email sara.klein@northwind passed the browser's own check. HTML5 email fields accept an address without a dot in the domain, so the type="email" check alone would let it through. Only the regular expression caught it.

Now change the email to "sara klein@northwind.example.com" with a space, and the phone to +1 415 555 0100. Click Create again:

Supplier Contact dialog with 1 error has occurred and a browser message below the email field about the space before the @
The browser's email check runs first.

This time the browser's email check found the space. APEX showed the browser's message in its own error style, and no request reached the server: the browser log of this test had no POST at all. The message text comes from the browser, so it depends on the browser and its language.

Finally, correct the email to sara.klein@northwind.example.com and click Create. The dialog closes, and Sara Klein appears in the report:

Supplier Contacts report with the new contact Sara Klein, email sara.klein@northwind.example.com and phone +1 415 555 0100
A valid contact is saved.

Next, create Ken Ito for Alpine Components with the email ken.ito@alpine.example.it and an empty phone. The contact is saved: the Phone format validation does not fail on an empty value, just like the check constraint. Both new rows are in the table:

Example:

select contact_id, full_name, email, nvl(phone, '(none)') as phone
from   supplier_contacts
where  full_name in ('Sara Klein', 'Ken Ito', 'Laura C.')
order  by contact_id;

Output:

   CONTACT_ID FULL_NAME     EMAIL                               PHONE
_____________ _____________ ___________________________________ __________________
           21 Sara Klein    sara.klein@northwind.example.com    +1 415 555 0100
           22 Ken Ito       ken.ito@alpine.example.it           (none)

The new IDs do not follow on from 5 because the identity column hands out cached values. That is normal and does not affect the example.

One more test: a new contact with the email Laura.Chen@Northwind.example.com. The format is valid, so both validations pass. The unique index then rejects the row, and the form shows "ORA-00001: unique constraint (APXSOL.SUPPLIER_CONTACTS_EMAIL_UK) violated" in the notification. Format checks do not find duplicates; the index does.

Things to Know

  • Item matches Regular Expression runs on the server after submit. The HTML5 email check runs in the browser before submit and is less strict: it let sara.klein@northwind through. Keep the server check.
  • Write the same pattern in the validation and in the check constraint. The validation gives a friendly message; the constraint protects data loaded by scripts, APIs and imports.
  • Oracle does not read \d as a digit inside brackets. Use [0-9] or [[:digit:]] there, and anchor every pattern with ^ and $ (Step 2).
  • No regular expression proves that a mailbox exists. To be sure, send a confirmation link or code to the address.
  • A duplicate email still ends in a raw ORA-00001 message. Add a separate validation that looks for an existing row, or translate the error in an error handling function.

Related Guides

Conclusion

Format checks in Oracle APEX need no code. The Item matches Regular Expression validation shows a clear message below the field, the E-Mail and Phone Number subtypes give the right keyboard and a first browser check, and placeholders and help text show the expected format.

The same patterns in REGEXP_LIKE check constraints keep the table clean when data arrives from anywhere else. The tests showed each layer at work: the browser stopped a space, the regular expression stopped a missing top-level domain and a phone without a country code, and the database stopped bad rows inserted in SQL.

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