In many Salesforce projects, developers often start by hardcoding configuration values (like API URLs, keys, or feature toggles) directly into Apex classes. While this might seem quick and simple at first, it creates long-term maintenance problems.
- Changing values requires code edits and deployments.
- Supporting multiple environments or services becomes tricky.
- Non-developers (like admins) cannot update these values.
A much better approach is to use Custom Metadata Types (CMDT) to externalize configurations. This keeps your Apex clean, makes maintenance easier, and empowers admins to manage settings without touching code.
Example Use Case: Cloud Integration Endpoints
Let’s take the example of integrating Salesforce with multiple cloud storage providers. Initially, you might write something like this.
private final static String uploadEndpoint = 'https://.../s3-salesforce-integration';
private final static String downloadEndpoint = 'https://.../s3-salesforce-integration-download';
private final static String deleteEndpoint = 'https://.../s3-salesforce-integration-delete';
This works fine until you add more providers (Google Drive, OneDrive, etc.) or need to update the URLs.
Defining Custom Metadata
To make this flexible, create a Custom Metadata Type (e.g. Integration_Config__mdt) with fields like:
- Upload_Endpoint__c
- Download_Endpoint__c
- Delete_Endpoint__c
Then, create records for each provider.
- AWS
- OneDrive
The Pitfall - Initialization Error
My first attempt to pull metadata into Apex looked like this.
Integration_Config__mdt awsConfig = Integration_Config__mdt.getInstance('AWS');
private static final String uploadEndpoint = awsConfig.Upload_Endpoint__c;
private static final String downloadEndpoint = awsConfig.Download_Endpoint__c;
private static final String deleteEndpoint = awsConfig.Delete_Endpoint__c;
But Salesforce gave an error on deployment.
Variable does not exist: awsConfig
That’s because you can’t directly use a runtime fetched variable (getInstance) to initialize static final variables at the class level.
The Solution - Static Initializer Block
The correct way is to use a static initializer block, which executes once when the class is loaded.
private static final String uploadEndpoint;
private static final String downloadEndpoint;
private static final String deleteEndpoint;
static {
Integration_Config__mdt awsConfig = Integration_Config__mdt.getInstance('AWS');
if (awsConfig != null) {
uploadEndpoint = awsConfig.Upload_Endpoint__c;
downloadEndpoint = awsConfig.Download_Endpoint__c;
deleteEndpoint = awsConfig.Delete_Endpoint__c;
}
}
Now the values are dynamically loaded from metadata and available across all methods.
Before vs After
1. Hardcoded Configuration (Rigid & Risky)
Apex Class
├── Upload URL -> hardcoded
├── Download URL -> hardcoded
└── Delete URL -> hardcoded
- Requires redeployment for every change.
- Hard to scale for multiple providers.
- Not manageable by admins.
2. Metadata-Driven Configuration (Flexible & Scalable)
Custom Metadata (Integration_Config_mdt)
├── Record: AWS
│ ├── Upload_Endpointc
│ ├── Download_Endpointc
│ └── Delete_Endpoint_c
├── Record: Google
├── Record: OneDrive
└── ...
Apex Class
└── Reads values dynamically using static block
- Values stored in metadata records.
- Easy to update without touching code.
- Supports multiple configurations.
- Admin friendly.
Key Takeaways
- Avoid hardcoding any configuration values (URLs, IDs, toggles, etc.) in Apex.
- Use Custom Metadata Types to make your code flexible and environment friendly.
- Remember, static variables that depend on metadata should be initialized inside a static block, not inline.
With this approach, you’ll make your Apex code more maintainable, scalable, and admin friendly whether you’re managing API endpoints, feature flags, or any other configuration values.
Top comments (0)