Why the ApexPage Metadata Type Still Matters in Modern Orgs
While Lightning Web Components (LightningComponentBundle) are the default UI framework for modern Salesforce development, ApexPage (Visualforce) remains an indispensable architectural building block. In enterprise orgs, architects still rely on ApexPage for capabilities that LWC cannot natively perform on the server:
- Server-Side PDF Generation (
renderAs="pdf"): Generating invoices, quotes, and compliance certificates directly intoBlobcontent viaPageReference.getContentAsPDF(). - List View Mass Action Buttons: Passing selected record IDs from a standard list view into a
StandardSetControllerusing a customWebLinkbutton. - Lightning Out & Canvas Host Containers: Hosting external scripts or isolated origins that cannot run inside Lightning Web Security (LWS).
- Custom OAuth Callback & Payment Redirect Pages: Handling server-side query parameters before redirecting back to Lightning Experience.
SFDX Source Format & File Structure
Every ApexPage consists of two paired files in the pages/ directory:
pages/<PageName>.page: The Visualforce XML/HTML markup starting with<apex:page>.pages/<PageName>.page-meta.xml: The metadata companion file defining the API version, label, and security flags.
Key Metadata Fields on ApexPage
| Field | Type | Required | Architectural Purpose |
|---|---|---|---|
apiVersion |
double |
Yes | The Salesforce API version used to compile the Visualforce page and evaluate standard components. |
label |
string |
Yes | The human-readable label displayed in Setup, Lightning App Builder, and Permission Sets. |
availableInTouch |
boolean |
No | Enables the page for Lightning Experience, the Salesforce Mobile App, and Lightning Utility Bars. Defaults to false. |
confirmationTokenRequired |
boolean |
No | Requires a CSRF token on GET requests to the page. Critical if your <apex:page action="{!init}"> method performs DML or state changes. |
description |
string |
No | Documents the architectural purpose of the page (up to 255 characters). |
packageVersions |
PackageVersion[] |
No | Pins the managed package version (namespace, majorNumber, minorNumber) referenced by the page. |
Real-World XML & Markup Example
1. Metadata File (pages/InvoiceStatementPdf.page-meta.xml)
<?xml version="1.0" encoding="UTF-8"?>
<ApexPage xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>68.0</apiVersion>
<availableInTouch>true</availableInTouch>
<confirmationTokenRequired>true</confirmationTokenRequired>
<description>Renders an official customer invoice statement as a downloadable PDF via Flying Saucer engine.</description>
<label>Invoice Statement PDF</label>
</ApexPage>2. Visualforce Markup (pages/InvoiceStatementPdf.page)
<apex:page standardController="Account"
extensions="InvoiceStatementController"
renderAs="pdf"
applyBodyTag="false"
showHeader="false">
<html>
<head>
<style type="text/css">
@page { size: A4 portrait; margin: 20mm; }
body { font-family: Arial Unicode MS, sans-serif; font-size: 10pt; }
</style>
</head>
<body>
<h1>Statement for {!Account.Name}</h1>
<c:InvoiceLineItemTable accountId="{!Account.Id}" />
</body>
</html>
</apex:page>Warning
Enabling confirmationTokenRequired Breaks Hardcoded URLs: When <confirmationTokenRequired>true</confirmationTokenRequired> is enabled, navigating to /apex/InvoiceStatementPdf?id=001... using a plain HTML string URL without using URLFOR($Page.InvoiceStatementPdf, null, [id=Account.Id]) or an ApexPages.PageReference in Apex will fail with an invalid session/CSRF error.
Querying ApexPage with SOQL & Tooling API
ApexPage is directly queryable via standard SOQL and the Tooling API. This makes it easy to audit legacy Visualforce pages for security compliance, outdated API versions, or missing CSRF protection.
1. Find Visualforce Pages Missing CSRF Protection or Lightning Readiness
SELECT Id, Name, MasterLabel, ApiVersion, ControllerType, ControllerKey,
IsAvailableInTouch, IsConfirmationTokenRequired, LastModifiedDate
FROM ApexPage
WHERE NamespacePrefix = null
AND (IsConfirmationTokenRequired = false OR ApiVersion < 55.0)
ORDER BY ApiVersion ASC2. Search Visualforce Page Markup for Deprecated JavaScript Libraries
SELECT Name, MasterLabel, ApiVersion, ControllerKey
FROM ApexPage
WHERE NamespacePrefix = null
ORDER BY Name ASC(Note: You can include the Markup field in a targeted query on specific pages to scan for inline <script> tags or sforce.connection AJAX Toolkit usage.)
3. Audit Which Permission Sets Grant Access to a Visualforce Page
SELECT Parent.Name, Parent.Label, Parent.IsOwnedByProfile
FROM SetupEntityAccess
WHERE SetupEntityType = 'ApexPage'
AND SetupEntityId IN (
SELECT Id FROM ApexPage WHERE Name = 'InvoiceStatementPdf'
)Senior Architect Best Practices & Gotchas
- Always Grant Access via
PermissionSet(pageAccesses): Users without explicitApexPageaccess in a Permission Set or Profile will receive anInsufficient Privilegeserror—even if an Apex class callsgetContentAsPDF()in user context. - Avoid DML in
<apex:page action="...">: AppExchange Security Review flags any Visualforce page that executes DML inside its page-loadactionmethod unlessconfirmationTokenRequiredistrueor the logic is moved to a postback command action. - Keep Reusable PDF Fragments in
ApexComponent: Instead of duplicating CSS and table markup across multiple PDF pages, encapsulate shared sections inside an ApexComponent.
Frequently asked questions
What is the purpose of the confirmationTokenRequired field on ApexPage?
When confirmationTokenRequired is set to true, Salesforce requires a valid CSRF confirmation token in the URL before executing any GET request to the Visualforce page. This protects pages that perform DML in an action attribute from Cross-Site Request Forgery attacks.
Why is my Visualforce page not appearing as an option for a Lightning App Builder tab or Quick Action?
A Visualforce page only appears in Lightning App Builder, the Salesforce Mobile App, and Lightning Quick Actions when the availableInTouch field in its .page-meta.xml file is set to true.
Why do architects still create new ApexPage components in Lightning Web Component architectures?
Even in modern LWC-first orgs, Visualforce pages remain the standard native mechanism for server-side PDF generation via renderAs pdf, embedding third-party iframes with relaxed CSP requirements, and overriding List View mass action buttons.
Can you query Visualforce pages and their markup using SOQL?
Yes. You can query the ApexPage standard object in both standard SOQL and the Tooling API to inspect ApiVersion, ControllerType, ControllerKey, IsConfirmationTokenRequired, and the full Markup body.
How are Apex क्लास version dependencies stored in ApexPage metadata?
If a Visualforce page references components or classes from an installed managed package, the .page-meta.xml file tracks the exact package version using packageVersions elements with namespace, majorNumber, and minorNumber.
Comments
Questions, corrections and tips are welcome. Comments are GitHub Discussions, so you sign in with GitHub.