Overview
The ApexClass metadata type represents a server-side Apex class in your Salesforce org. Every Apex class in an SFDX project consists of two paired files inside force-app/main/default/classes/: the source code file (ClassName.cls) and its accompanying XML metadata file (ClassName.cls-meta.xml).
The .cls-meta.xml file controls three vital execution attributes: the pinned apiVersion (which governs SOQL behavior, sharing defaults, and runtime semantics), the lifecycle status (Active or Deleted), and packageVersions bindings when referencing managed packages. Because ApexClass is exposed in both the standard Object Reference and the Tooling API, you can query class sizes, API versions, compilation validity, and test coverage directly via SOQL.
As shown in the deployment workflow above, deploying an ApexClass compiles the source pair into platform bytecode, enforces the 75% org-wide code coverage gate in production, and requires classAccesses assignment in a PermissionSet for UI or REST execution.
Prerequisites
- Permissions: “Author Apex” and “Modify Metadata Through Metadata API Functions” permissions.
- Test Coverage for Production: At least 75% of all executable lines across the org must be covered by passing unit tests, and every trigger executed must have >0% coverage.
- API Version: Verified against API version
68.0(Winter ’27).
ApexClass XML Metadata Structure
The ClassName.cls-meta.xml file is concise, containing only three top-level elements:
| Field | Type | Required | Description & Architectural Notes |
|---|---|---|---|
apiVersion |
double |
✅ Yes | Binds the class to a specific Salesforce release (e.g., 68.0). Keep classes updated to avoid legacy runtime quirks and ICU locale issues. |
status |
ApexCodeUnitStatus |
✅ Yes | Either Active or Deleted. (Inactive is not valid on ApexClass; it is only valid on ApexTrigger). |
packageVersions |
PackageVersion[] |
No | Pins the namespace, majorNumber, and minorNumber of an installed managed package referenced by this class. |
Tip
Salesforce enforces a minimum API version of 45.0 for all classes and triggers when enabling ICU Locale Formats. Regularly audit and upgrade old classes to the current release (68.0).
Querying ApexClass and Code Coverage via SOQL
Unlike most metadata types, ApexClass is directly queryable in standard SOQL as well as the Tooling API.
1. Audit Classes on Legacy API Versions and Check Code Size (Standard SOQL)
Find all unmanaged classes running on older API versions or consuming excessive character limits toward the 6 MB org ceiling:
SELECT Id, Name, ApiVersion, Status, IsValid,
LengthWithoutComments, LastModifiedDate, LastModifiedBy.Name
FROM ApexClass
WHERE NamespacePrefix = null
AND ApiVersion < 60.0
ORDER BY LengthWithoutComments DESC2. Calculate Org-Wide Character Usage Against the 6 MB Limit (Standard SOQL)
Test classes (@IsTest) do not count toward the 6 MB (6,000,000 characters) org Apex limit. Query non-test classes by inspecting LengthWithoutComments:
SELECT SUM(LengthWithoutComments) totalCharactersUsed
FROM ApexClass
WHERE NamespacePrefix = null3. Inspect Class-by-Class Test Coverage via Tooling API
Run this query with sf data query --use-tooling-api to identify classes with fewer than 75% covered lines before a production deployment:
SELECT ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered
FROM ApexCodeCoverageAggregate
WHERE NumLinesUncovered > 0
ORDER BY NumLinesUncovered DESCExample
The following example pairs a bulkified, user-mode service class (InvoiceBillingService.cls) with its 68.0 metadata file (InvoiceBillingService.cls-meta.xml) and a 100%-coverage unit test (InvoiceBillingServiceTest.cls).
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>68.0</apiVersion>
<status>Active</status>
</ApexClass>public with sharing class InvoiceBillingService {
/**
* Marks open accounts as Priority when annual revenue exceeds threshold.
* Enforces CRUD and FLS via WITH USER_MODE and as user DML.
*/
@AuraEnabled
public static List<Account> elevateHighValueAccounts(Set<Id> accountIds, Decimal revenueThreshold) {
if (accountIds == null || accountIds.isEmpty()) {
return new List<Account>();
}
List<Account> eligibleAccounts = [
SELECT Id, Name, AnnualRevenue, Rating
FROM Account
WHERE Id IN :accountIds
AND AnnualRevenue >= :revenueThreshold
WITH USER_MODE
];
for (Account acc : eligibleAccounts) {
acc.Rating = 'Hot';
}
if (!eligibleAccounts.isEmpty()) {
update as user eligibleAccounts;
}
return eligibleAccounts;
}
}@IsTest
private class InvoiceBillingServiceTest {
@IsTest
static void elevatesHighValueAccountsInBulk() {
Account highAcc = new Account(Name = 'Enterprise Corp', AnnualRevenue = 2500000);
Account lowAcc = new Account(Name = 'Startup LLC', AnnualRevenue = 50000);
insert new List<Account>{ highAcc, lowAcc };
Test.startTest();
List<Account> updated = InvoiceBillingService.elevateHighValueAccounts(
new Set<Id>{ highAcc.Id, lowAcc.Id },
1000000
);
Test.stopTest();
Assert.areEqual(1, updated.size(), 'Only the high-revenue account should be elevated');
Account refreshed = [SELECT Rating FROM Account WHERE Id = :highAcc.Id];
Assert.areEqual('Hot', refreshed.Rating);
}
}Limits and Gotchas
- Org-Wide Code Size Limit: 6 MB (6,000,000 characters) of active Apex code (classes and triggers combined), excluding comments and classes annotated with
@IsTest. - Single Class Size Limit: 1,000,000 characters maximum per
ApexClass. - Active Job Deployment Lock: Deploying an
ApexClassthat has a running or queuedBatchable,Queueable,Schedulable, or@futurejob fails unless you enable Allow deployments of components when corresponding Apex jobs are pending or in progress under Setup → Deployment Settings (or in DeploymentSettings metadata). - No CRUD Calls in Metadata API: Unlike configuration metadata,
ApexClasscannot be deployed using synchronouscreateMetadata()orupdateMetadata()CRUD calls in Production; you must use asynchronousdeploy()orsf project deploy startso unit tests execute. - Naming Rules: The
fullNamecan only contain alphanumeric characters and single underscores (_), must begin with a letter, and cannot end with an underscore or contain two consecutive underscores (__).
Related Official Documentation
- ApexClass Metadata API Reference: Metadata XML schema and
PackageVersionstructure. - ApexClass Object Reference: Standard SOQL fields for querying
ApexClassrecords. - ApexCodeCoverageAggregate Tooling API: Querying org and class test coverage metrics.
Frequently asked questions
Can I set an ApexClass status to Inactive in its .cls-meta.xml file?
No. While the ApexCodeUnitStatus enumeration includes Inactive, Salesforce only allows Inactive on ApexTrigger components. Valid status values for ApexClass are Active and Deleted.
How do I delete an ApexClass in Production using Metadata API?
In a managed package upgrade, change the status field in .cls-meta.xml to Deleted. In unmanaged production orgs, list the class in a destructiveChanges.xml manifest or run sf project delete source.
Why does deploying an ApexClass fail when a Scheduled or Batch job is running?
By default, Salesforce blocks recompiling an ApexClass that has pending or in-progress asynchronous jobs in AsyncApexJob. Enable Allow deployments of components when corresponding Apex jobs are pending or in progress in Deployment Settings to bypass this lock.
Why can an Aura or LWC component not call my @AuraEnabled Apex method after deployment?
Users must be granted explicit ApexClass access via a PermissionSet or Profile under classAccesses before they can invoke @AuraEnabled or REST-exposed Apex methods.
How do I find Apex classes running on old API versions using SOQL?
Query the standard ApexClass object in SOQL filtering for NamespacePrefix = null and ApiVersion less than 60.0, selecting Name, ApiVersion, and LengthWithoutComments.
What is the maximum character limit for a single ApexClass?
A single Apex class can contain up to 1,000,000 characters of code excluding comments and @IsTest annotated classes, within the total org Apex code limit of 6 MB.
Comments
Questions, corrections and tips are welcome. Comments are GitHub Discussions, so you sign in with GitHub.