Replacing Hardcoded Apex Configuration with Custom Metadata Types
Learn how to remove hardcoded values from Apex by implementing Custom Metadata Types, reducing deployment risks and allowing admins to manage environment settings.
30 Mar 2026, 00:10 UTC

The Problem: The Deployment Bottleneck of Hardcoded Values
Hardcoding environment-specific values—such as API endpoints, integration timeouts, or feature flags—directly into Apex classes creates a deployment bottleneck. Every change to a configuration value requires a full development cycle: updating the code, committing to version control, deploying to a sandbox, and finally promoting to production. This increases the risk of deploying the wrong environment variable to production and makes it impossible for administrators to toggle features without developer intervention.
The solution is to move these values into Custom Metadata Types (CMTs). Unlike Custom Objects, Custom Metadata records are treated as metadata, meaning they can be deployed via Change Sets or the Salesforce CLI without needing a separate data migration script.
Prerequisites
- Permissions: "Customize Application" and "Author Apex" permissions in the target Salesforce org.
- Environment: A Salesforce org (Developer, Sandbox, or Production) with an existing Apex class containing hardcoded literals.
- Tools: Access to the Salesforce Setup menu and the Developer Console or Salesforce CLI.
Step-by-Step Implementation
1. Define the Custom Metadata Type
First, create the structure that will hold your configuration.
- Navigate to Setup → Custom Metadata Types.
- Click New Custom Metadata Type.
- Enter a Label (e.g.,
App Configuration) and a Plural Label. - Save the record, then create Custom Fields for the values you need (e.g., a Text field named
Value__cor a Checkbox forIs_Enabled__c).
2. Populate Environment Records
Create the actual records that the code will query. Use the DeveloperName field as a unique key to identify the specific setting.
- Click Manage Records on your Custom Metadata Type.
- Click New.
- Set the Label to
Integration_Endpointand the Developer Name toIntegration_Endpoint. - Enter the environment-specific value in the
Value__cfield (e.g.,https://api.sandbox.example.com).
3. Refactor Apex to Query Metadata
Replace the hardcoded string in your Apex class with a SOQL query. To avoid hitting governor limits, query the metadata once and store it in a variable or a static cache.
// BEFORE: Hardcoded approach
public class IntegrationService {
public void sendData() {
String endpoint = 'https://api.production.example.com'; // Hardcoded risk
// Logic to call endpoint
}
}
// AFTER: Custom Metadata approach
public class IntegrationService {
private static final String ENDPOINT_KEY = 'Integration_Endpoint';
public void sendData() {
// Query the CMT record by its DeveloperName
App_Configuration__mdt config = [SELECT Value__c
FROM App_Configuration__mdt
WHERE DeveloperName = :ENDPOINT_KEY
LIMIT 1];
String endpoint = config.Value__c;
// Logic to call endpoint
}
}
Verification and Testing
To verify the implementation, run a query in the Developer Console (Query Editor) to ensure the records exist:
SELECT DeveloperName, Value__c FROM App_Configuration__mdt
Next, execute a snippet of Anonymous Apex to confirm the class retrieves the correct value:
IntegrationService service = new IntegrationService();
// Use System.debug to verify the endpoint value in the logs
System.debug('Current Endpoint: ' + [SELECT Value__c FROM App_Configuration__mdt WHERE DeveloperName = 'Integration_Endpoint'].Value__c);
Critical Engineering Considerations
Handling Missing Records
If a query for a Custom Metadata record returns no rows, Salesforce throws a QueryException. To prevent system crashes, implement a fallback mechanism:
List<App_Configuration__mdt> configs = [SELECT Value__c FROM App_Configuration__mdt WHERE DeveloperName = :ENDPOINT_KEY];
String endpoint = configs.isEmpty() ? 'https://fallback.example.com' : configs[0].Value__c;
Apex Test Data
Custom Metadata records are visible to Apex tests without needing seeAllData=true. However, if your tests rely on specific values that differ between sandboxes, your tests may fail during deployment. Use a mocking framework or a wrapper class to inject configuration values during unit tests to ensure consistency.
Performance and Limits
While CMT queries are efficient, they still count toward SOQL limits. Avoid placing CMT queries inside for loops. Instead, query all necessary configuration records into a Map at the start of the transaction:
Map<String, App_Configuration__mdt> configMap = new Map<String, App_Configuration__mdt>([SELECT DeveloperName, Value__c FROM App_Configuration__mdt]);
Rollback Plan
If the new configuration causes a production failure:
- Metadata Fix: If the value is incorrect, update the Custom Metadata record via Setup. This takes effect immediately without a deployment.
- Code Reversion: If the query logic is flawed, redeploy the previous version of the Apex class from your version control system (Git).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.