Take one small step with APEXlang to see why it’s a giant leap for low-code. In addition to the web-based Builder, now you can also maintain and generate Oracle APEX apps with your favorite coding tools and agents. This article gives a first peek at the following tools in action:
Familiar Experience, New Editing Options
APEX Builder is a productive way to deliver apps quickly. You configure smart data components by choosing a table, view, or query and adjusting declarative options. Then, you add the business logic only you could write.
But editing apps with external tools was not as friendly. You could export an app to SQL for source control, but settings were buried in internal API calls and sprinkled with numerical IDs. Readable YAML simplified reviews and diffs, but you still couldn’t edit or import it. To preserve the integrity of your app definitions, APEX didn’t support manual updates since it couldn’t reliably validate these files. Until today.
Starting with APEX 26.1, you now have the best of both worlds.
Use APEX Builder for everything you love about it. Whenever useful, you can review and edit your app’s APEXlang definitions in your favorite editor. Use SQLcl to export, import, and validate apps from the command line or in scripts. In VS Code, the SQL Developer extension helps you export, edit, validate, and import your apps, showing problems proactively so they’re easy to fix.
Agentic Assistance: Generate, Review, Evolve
APEXlang is well-structured and easy to understand, so you can also partner productively with AI coding agents like Claude, Codex, and others. Ask them to review your app, suggest improvements, or edit existing definitions. With Oracle’s APEXlang skills from GitHub, just explain what you need to your AI collaborator. It can help generate pages and components, make incremental changes, and find and fix problems on its own using SQLcl’s apex validate command.
NOTE: To see any image in full size, right click to open it in a new tab.
Exporting APEXlang in the Builder
Imagine using the Create App Wizard to build a simple APEX app with an Interactive Report page and modal Form page on the familiar EMP table.

To export the app in APEXlang, just choose that format on the Export page.

This downloads an employees.zip file. As shown below, it contains a file for each page and component. The application.apx file has app-level settings. The .apx files contain APEXlang. The deployments, pages, shared‑components subdirectories organize app components at the root level of the zip. Notice filenames for pages use both number and alias for additional clarity, and the compiler enforces the page alias matches the page number and alias in the file name. Static Application Files are included as-is along with a list of their names and metadata in static‑files.apx.
employees.zip├── application.apx├── page-groups.apx├── deployments│ └── default.json├── pages│ ├── p00000-global-page.apx│ ├── p00001-home.apx│ ├── p00002-employees.apx│ ├── p00003-employee.apx│ └── p09999-login.apx├── shared-components│ ├── static-files│ │ └── icons│ │ ├── app-icon-144-rounded.png│ │ ├── app-icon-192.png│ │ ├── app-icon-256-rounded.png│ │ ├── app-icon-32.png│ │ └── app-icon-512.png│ ├── themes│ │ └── universal-theme│ │ └── theme.apx│ ├── authentications.apx│ ├── authorizations.apx│ ├── breadcrumbs.apx│ ├── build-options.apx│ ├── component-settings.apx│ ├── lists.apx│ ├── lovs.apx│ └── static-files.apx└── .apex └── apexlang.json
What About the Application ID?
In APEXlang, the app id goes into the defaults.json file in the deployments subdirectory. This lets the downloaded file employees.zip use a more descriptive name based on the employeesapp alias. This separation also gives you the option to personalize each deployment environment with an appropriate set of environment-specific settings.
{ "app" : { "id" : 104, "runtime" : { "debugging" : true } }}
APEXlang: Metadata-Driven Validation
The APEXlang compiler knows which APEX components are available as well as what properties and child components each one has. It validates property values, nested components, and constraints on when each applies. For example, one type of region component like an interactiveReport has nested column definitions, but a form type region doesn’t. Similarly, a chart type region has a zoomAndScroll property, but a classicReport does not.
The APEX Page Designer has always reacted dynamically to show and hide appropriate properties and child components. Now the APEXlang compiler leverages the same details to strictly validate your app components and pages.
This information about APEX’s app metadata is called meta-metadata (MMD). Over time, it will grow to reflect new APEX features, so each app stores the current APEX meta-metadata version used to export it. This lets the APEXlang compiler validate app files correctly. It also ensures SQL Developer offers accurate code completion in VS Code.
Your app’s .apex/apexlang.json file records this MMD version. It’s important this version remain unchanged until a future APEX version might export your app with a new value. In short, don’t modify it by hand.
{ "mmdVersion" : "26.1.0+3102"}
Importing APEXlang in the Builder
You can import an app in APEXlang format in the Builder. Just drag or select its zip file on the Import page and click (Next >).

The Import wizard confirms the Type is APEXlang and gives the usual options to choose the application ID. Just click (Import Application) to proceed. APEX Builder validates and compiles the APEXlang source. If no errors are raised, the application imports successfully.

Compiling APEXlang via ORDS Service
APEX Builder uses a dedicated ORDS endpoint with Builder authentication to compile APEXlang artifacts. So, the first time you import an APEXlang format app in a workspace, you may be prompted to enable your workspace’s parsing schema for ORDS REST services. Just click the (REST Enable Schema) button and the wizard proceeds to the next step to continue the import.

First Peek at APEXlang Syntax
When you open an app folder with APEXlang files in your favorite editor, you can browse, search, and edit them like any project. For example, page 3 is an Employee form page with alias employee. Its filename incorporates both the page number and the alias. Opening p00003‑employee.apx, you see the wizard created a modalDialog page using the drawer template.
page 3 ( name: Employee alias: EMPLOYEE title: Employee appearance { pageMode: modalDialog dialogTemplate: @/drawer templateOptions: [ #DEFAULT# js-dialog-class-t-Drawer--pullOutEnd ] } dialog { chained: false } ⋮ )
In the example above, it’s easy to see this page component’s pageMode property – in the appearance group – has the value modalDialog. For brevity, we can say appearance.pageMode is modalDialog.
APEXlang avoids unnecessary delimiters. Every component (e.g. page) contains its properties, grouped by function, as name : value pairs. Properties like name, alias, and title in the identification group can optionally appear with no group as shown below. Required properties must have a value, but files stay concise since any property with its default value can be omitted. Leave optional properties out until you need to assign them a value.
Values don’t require quotes in most cases, and a new line separates properties and array elements. For example, the value of appearance.templateOptions is an array with two entries. In this case, they are CSS class names the template defines with the special #DEFAULT# placeholder for “any default classes” the template specifies.
page 3 ( name: Employee ⋮ appearance { ⋮ templateOptions: [ #DEFAULT# js-dialog-class-t-Drawer--pullOutEnd ] } ⋮ )
Later in this article, you’ll see how multi-line text appears in APEXlang for common cases like SQL, PL/SQL, JavaScript, HTML, and CSS.
Understanding the Identifier Property
Each component type’s metadata nominates an identifier property. Most of the time, staticId plays this role, but sometimes it’s the name or alias. In APEX 26.1, all components now require a human-readable static ID so APEXlang files never need to reference internal ID numbers. This new requirement is automatically handled for existing apps. On instance upgrade or app import, APEX assigns any apps created with prior platform versions a meaningful static ID automatically.
The APEXlang compiler distinguishes each component within a given scope using its identifier property value. It enforces component uniqueness, depending on the type, across the app, a page, or just within a parent. For example:
- an app can have only one page
3 - page
2and3can both have aregionwith identifieremployees - page
3can haveemployees&departmentsregions each with aDEPTNOcolumn - the
departmentsregion on page3cannot have twoDEPTNOcolumns.
A component’s identifier value follows its type, just before the opening parenthesis of its property list. In the example below 3 is the page component’s identifier, and employee identifies the region.
page 3 ( ⋮ region employee ( ⋮ ) ⋮)
Component References in APEXlang
Each @-prefixed value is a reference to the identifier property value of another component. Later in p00003‑employee.apx you find the page contains a Form region named Employee identified by employee. Notice its appearance.template property references the blank-with-attributes template.
page 3 ( ⋮ region employee ( name: Employee type: form source { location: localDatabase tableName: EMP } layout { sequence: 10 slot: contentBody } appearance { template: @/blank-with-attributes templateOptions: #DEFAULT# } edit { enabled: true allowedOperations: [ add update delete ] } ) ⋮)
Each page item appears in the page’s .apx file as well. Notice the hidden, primary key P3_EMPNO item based on the EMPNO column. It references the employee region as the value of both its layout.region and source.formRegion properties.
When a pageItem references an employee region on the same page, it uses @employee.
page 3 ( ⋮ pageItem P3_EMPNO ( type: hidden layout { sequence: 10 region: @employee slot: regionBody } source { formRegion: @employee column: EMPNO dataType: number queryOnly: true primaryKey: true } security { sessionStateProtection: checksumRequiredSessionLevel } ) ⋮)
Similarly, when a pageItem references a shared component like an LOV in the same app, it uses @. Notice the P3_DEPTNO item below is a selectList referencing the sharedComponent LOV using @dept-dname.
page 3 ( ⋮ pageItem P3_DEPTNO ( type: selectList label { label: Deptno alignment: left } lov { type: sharedComponent lov: @dept-dname } layout { sequence: 80 region: @employee slot: regionBody alignment: left } appearance { template: @/optional-floating templateOptions: #DEFAULT# } source { formRegion: @employee column: DEPTNO dataType: number } ) ⋮)
Two exceptions to the @‑prefix rule use an @/‑prefix instead. They are the components that are either:
- on the global page, or
- part of the standard Universal Theme.
For example, in a regular page, reference the my‑global‑page‑header region from the global page using @/my‑global‑page‑header.
When a template comes from the standard Universal Theme set, use references like @/drawer or @/blank‑with‑attributes or @/optional‑floating. In contrast, when a theme template is defined locally within the app, use @my‑slideshow.
Using Both Builder and External Tools
You can use APEX Builder as you normally would, but be mindful that the unit of APEXlang export is the application. If you have made changes to your app outside the builder, importing its APEXlang replaces the Builder’s current version to reflect the latest external changes. Conversely, if you make changes in APEX Builder, external tools see them when you next export the app to APEXlang.
Maintaining your app’s development history over time by source controlling its external artifacts in a system like Git continues to be a best practice. APEXlang files work great for this purpose, too.
If you realize your external APEXlang and APEX Builder changes are out of sync, now that APEXlang files are editable, validatable, and importable, you can resolve the situation by:
- Exporting the Builder app version to APEXlang
- Unzipping it into a temporary folder
- Unifying versions, resolving any conflicts, with your favorite diff/merge tool
- Importing the unified APEXlang back into the Builder when appropriate.
The app only imports when it validates cleanly, so if it fails just review the errors, address them, and try again.
Updating Employees Page in APEX Builder
Imagine using Page Designer in APEX Builder to evolve the initial Employees page. Say you make the following enhancements:
- switch the Interactive Report region to be a Content Row
- change it to be based on a SQL query,
- update the page help text with some HTML markup,
- define an inline CSS rule to increase the font size by 20%
- add a Raise Salary action button to run PL/SQL business logic, and
- use JavaScript on the Page Load event to welcome the user back to your app.
The figure below shows your page after these modifications. Since you haven’t made changes yet in VS Code, you can simply export the app to APEXlang directly in VS Code to reflect the latest Builder changes in the project files.

Exporting APEXlang with SQL Developer
In VS Code, SQL Developer shows APEX apps in a connection’s APEX folder. As an alternative to exporting from APEX Builder, choose Export… on an app’s context menu under the connection. Then pick a parent folder for the app’s APEXlang artifacts and click (Apply). If necessary, for our example here, SQL Developer creates the employees subdirectory in that parent folder and exports the app in APEXlang format into it.

If you’re curious, the SQL tab in the Export… panel shows the SQLcl command to do this APEXlang app export (N.B. wrapped on separate lines here for legibility). The -dir parameter is optional in practice. If omitted, SQLcl exports the app into a folder named after the app alias to the current directory.
apex export -applicationid 104 -dir '/Users/smuench/Downloads' -exptype APEXLANG

Since the /Users/smuench/Downloads/employees folder is already open in the editor, the project immediately updates to reflect the latest changes.
SQL in APEXlang
Peeking at the p00002-employees.apx page, you see how a SQL query appears in APEXlang. It uses “fenced code blocks” with a language indicator. The opening triple-backtick delimiter includes sql so the editor can give language-sensitive assistance. The code block ends with a closing triple-backtick.
page 2 ( name: Employees ⋮ region employees ( name: Employees type: themeTemplateComponent/contentRow source { location: localDatabase type: sqlQuery sqlQuery: ```sql select EMPNO, ENAME, DNAME from EMP_DEPT_V ``` } ⋮ ) ⋮)
PL/SQL in APEXlang
Similarly, PL/SQL in your app appears in a block with the plsql language indicator. In the example below, the logic is a single update statement that returns the adjusted employee salary into a hidden :P2_NEW_SALARY page item.
page 2 ( name: Employees ⋮ region employees ( name: Employees ⋮ action raise-salary ( position: primaryActions template: button label: Raise Salary layout { sequence: 10 } behavior { type: triggerAction } triggerAction increase-salary-by-100 ( name: Increase Salary by 100 action: executeServerSideCode settings { plsqlCode: ```plsql update emp set sal = nvl(sal,0) + 100 where empno = :EMPNO returning sal into :P2_NEW_SALARY; ``` itemsToSubmit: EMPNO itemsToReturn: P2_NEW_SALARY } execution { sequence: 10 } ) ⋮ ) ) ⋮)
JavaScript in APEXlang
Your page-load JavaScript runs in the browser, so it has a javascript‑browser language indicator. Any server-side JavaScript would use javascript‑mle instead.
page 2 ( name: Employees ⋮ dynamicAction on-page-load ( name: On Page Load execution { sequence: 20 } when { event: ready } action native-javascript-code ( action: executeJsCode settings { jsCode: ```javascript-browser if ( !sessionStorage.getItem( "p2Welcomed" ) ) { const username = apex.item( "P2_APP_USER" ).getValue(); apex.message.showPageSuccess( `Welcome back, ${username}!` ); sessionStorage.setItem( "p2Welcomed", "true" ); } ``` } execution { sequence: 10 } ) ) ⋮)
HTML and CSS in APEXlang
Finally, as shown below, any HTML and CSS you add to your page shows up in code blocks with html and css language indicators respectively. A CSS example looks like this:
page 2 ( name: Employees ⋮ css { inline: ```css body { font-size: 1.2rem; } ``` } ⋮)
And page help HTML markup looks similar.
page 2 ( name: Employees ⋮ help { helpText: ```html <p> Select an employee to edit it, or click (Raise Salary) to increase an employee's salary by 100. </p> ``` } ⋮)
Editing APEXlang in SQL Developer VS Code
Consider a simple example of editing an APEXlang page in VS Code. The SQL Developer extension adds APEXlang assistance for .apx files. For instance, in the figure below we’ve positioned the cursor in the existing value of the appearance.dialogTemplate property of page 3. The (Ctrl) + (Space) key combo summons code completion. Notice it shows the valid templates available for this page whose pageMode is modalDialog. We switch the page from a drawer template to a modal dialog one by choosing @/modal-dialog from this list.

.apx FileProblems Panel Proactively Presents Errors
After changing the page to use the modal-dialog template, templateOptions gets a squiggly underline and the Problems panel shows “Invalid template option value…” This validation error indicates one of the existing values is no longer applicable to a modal dialog.

Importing APEXlang into APEX Builder
After correcting the templateOptions value, optionally removing the square brackets for a one-element array, we’re ready to import the changed application into APEX Builder. As shown below, just click the “play” button in toolbar in the upper right of the editor. Since importing an APEX app requires a connection to your APEX workspace’s parsing schema, SQL Developer prompts you to choose one if needed. As always, importing an app overwrites the current version in APEX Builder so ensure all Builder changes are already reflected in the APEXlang you’re about to import.

If the app validates successfully, then the import succeeds and a confirmation appears.

Refreshing the application page in the browser, you see the Employee page has changed from a modal drawer to a modal dialog.

Exporting APEXlang Using SQLcl
You can also export, import, and validate your APEX apps in APEXlang using the SQLcl command line.
To export an app in APEXlang format, connect to your workspace’s parsing schema, use the apex export command, and indicate the application id and export type ofapexlang.
SQL> apex export -applicationid 104 -exptype apexlang
Importing APEXlang with SQLcl
To import an APEXlang application with SQLcl, connect to your workspace’s parsing schema, use apex import, and indicate the input to load as a path to a directory or zip file:
SQL> apex import -input /Users/smuench/Downloads/employees
Importing an APEXlang app always validates it first, so any errors or warnings appear as with apex validate below. If no errors are detected, SQLcl proceeds. If the import is successful, you’ll see:
SQL> apex import -input /Users/smuench/Downloads/employeesImporting application ID: 104 into workspace: DIVEINTOAPEXImport successful.
Validating APEXlang Using SQLcl
To validate an APEXlang application in SQLcl without importing it, connect to your workspace’s parsing schema, use apex validate, and indicate the input to verify as a path to a directory or zip file:
SQL> apex validate -input /Users/smuench/Downloads/employees
You’ll see Validation successful, otherwise any errors or warnings appear in the SQLcl console.
For example, suppose we edit the pageItem P3_EMPNO section of the p00003‑employee.apx page to introduce three typos as shown below and save the file:
@employee->@employe(remove the trailinge)number->numbe(remove the trailingr)primaryKey->primaryKe(remove the trailingy)
page 3 ( ⋮ pageItem P3_EMPNO ( type: hidden layout { sequence: 10 region: @employe <-- 1 -- slot: regionBody } source { formRegion: @employee column: EMPNO dataType: numbe <-- 2 -- queryOnly: true primaryKe: true <-- 3 -- } security { sessionStateProtection: checksumRequiredSessionLeve } ) ⋮)
The errors display immediately in the VS Code Problems panel, and apex validate shows them as follows in SQLcl:
SQL> apex validate -input /Users/smuench/Downloads/employeesAPEXLang Compile Errors:File: pages/p00003-employee.apxLine: 123Column: 12Type: LOV_NOT_FOUNDError: Invalid LOV required parameter: source - dataType (string)Valid parameters are: -bfile-blob-boolean-clob-date-intervalDayToSecond-intervalYearToMonth-number-rowid-sdoGeometry-timestamp-timestampWithLocalTimeZone-timestampWithTimeZone-varchar2File: pages/p00003-employee.apxLine: 117Column: 12Type: REFERENCE_NOT_FOUNDError: Reference not found: @employeFile: pages/p00003-employee.apxLine: 125Column: 12Type: INVALID_PROPERTYError: Invalid property: primaryKe
To collect any errors or warnings for later reference, use the SQLcl spool command to capture the console output into a file.
SQL> spool employees_errors.logSQL> apex validate -input /Users/smuench/Downloads/employeesAPEXLang Compile Errors:File: pages/p00003-employee.apxLine: 123Column: 12Type: LOV_NOT_FOUNDError: Invalid LOV required parameter: source - dataType (string) ⋮ SQL> spool off
Reviewing APEXlang Errors in APEX Builder
Even when importing an APEXlang application using APEX Builder, if the APEXlang compiler detects any errors you can review them and optionally download them in CSV format. In this case, the import does not occur. If you had indicated to overwrite an existing app, it remains unchanged. If you work with an AI coding agent, try giving it the CSV file and ask for a clickable list of errors, or for help in resolving them!

Summary
APEXlang gives APEX developers a practical new option for working with their apps both inside APEX Builder and with external tools. It offers declarative definitions you can read, edit, validate, version, and move through your usual development flow with confidence. SQLcl, ORDS, and SQL Developer now share a common APEXlang compiler, driven off the latest meta-metadata that has powered Page Designer for years. This makes external editing feel natural and safe, with strict validation, code completion, and integrated import/export support.
And this is only the beginning. In my next article, we’ll explore using apex validate to find and fix common metadata inconsistencies that may be hiding undetected in mature APEX apps. In another installment, we’ll look more closely at how the new APEX skills for AI coding agents can help you use natural language to generate, review, and evolve APEX apps. To get the APEX skills, just use the new skills sync command in SQLcl 26.1.2.
