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:

  1. Server-Side PDF Generation (renderAs="pdf"): Generating invoices, quotes, and compliance certificates directly into Blob content via PageReference.getContentAsPDF().
  2. List View Mass Action Buttons: Passing selected record IDs from a standard list view into a StandardSetController using a custom WebLink button.
  3. Lightning Out & Canvas Host Containers: Hosting external scripts or isolated origins that cannot run inside Lightning Web Security (LWS).
  4. Custom OAuth Callback & Payment Redirect Pages: Handling server-side query parameters before redirecting back to Lightning Experience.
force-app/main/default/pages/ Salesforce Platform InvoicePdf.page(Visualforce Markup) InvoicePdf.page-meta.xml(API Version, Touch, CSRF) Apex Controller(InvoiceController.cls) PermissionSet(pageAccesses) PageReference.getContentAsPDF()

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
<?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)

XML
<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

SQL
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 ASC

2. Search Visualforce Page Markup for Deprecated JavaScript Libraries

SQL
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

SQL
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 explicit ApexPage access in a Permission Set or Profile will receive an Insufficient Privileges error—even if an Apex class calls getContentAsPDF() in user context.
  • Avoid DML in <apex:page action="...">: AppExchange Security Review flags any Visualforce page that executes DML inside its page-load action method unless confirmationTokenRequired is true or 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.