Custom Metadata Types as Runtime AI Configuration
Overview
Custom Metadata Types (CMDTs) allow you to store configuration values -- such as AI model names, temperature settings, and token limits -- as deployable metadata records rather than hard-coded values in Apex. This means admins and architects can adjust runtime behavior through Salesforce Setup without requiring a code deployment.
This article covers why CMDTs are the best fit for AI feature configuration compared to Custom Settings and Custom Objects, how to structure them by concern (technical settings vs. behavior rules vs. UI prompts), and how to implement a cached Apex query pattern with fallback defaults. Whether you are building AI-powered features or any configurable automation, these patterns keep your solutions flexible and maintainable.
Custom Metadata Types as Runtime AI Configuration
When building AI-powered features in Salesforce, configuration values like model names, temperature settings, token limits, and greeting messages change frequently during development and tuning. Hard-coding these values in Apex means every tweak requires a code deployment. Custom Metadata Types (CMDTs) solve this by externalizing configuration into deployable, queryable records.
Why CMDTs Over Alternatives
| Feature | Custom Metadata | Custom Settings | Custom Objects |
|---|---|---|---|
| Deployable via change sets/packages | Yes | Hierarchy only | No |
| Available in Apex without SOQL limits | Yes (cached) | Yes | No |
| Needs DML in test setup | No | Yes | Yes |
| Accessible in formulas & validation rules | Yes | Yes | No |
| Supports multiple record types | Via DeveloperName | Limited | Yes |
CMDTs are the strongest fit for AI configuration because they deploy with your metadata, require no test data setup, and do not count against SOQL governor limits.
Architecture: Separate CMDTs by Concern
Rather than a single monolithic CMDT, split configuration into purpose-specific types:
AI_Config__mdt-- Runtime settings: model API name, temperature, max tokens, monthly budget, feature flagsAI_Context__mdt-- Behavior rules: system prompt sections, conversation flow instructions, anti-patternsAI_Suggested_Prompt__mdt-- UI starter prompts: label, description, icon, sort order, active flag
This separation allows different teams to own different aspects. A prompt designer can modify AI_Context__mdt records without touching the technical configuration in AI_Config__mdt.
Cached Query with Fallback Defaults
The standard Apex pattern caches the CMDT query result to avoid redundant calls within a transaction, and provides sensible defaults if the CMDT records have not been deployed yet.
public class AIConfigService {
private static AI_Config__mdt cachedConfig;
public static AI_Config__mdt getConfig() {
if (cachedConfig != null) {
return cachedConfig;
}
List<AI_Config__mdt> configs = [
SELECT Model_API_Name__c, Max_Response_Tokens__c, Temperature__c,
Chatbot_Name__c, Monthly_Token_Budget__c, Is_Active__c
FROM AI_Config__mdt
WHERE DeveloperName = 'Portal_Chatbot'
LIMIT 1
];
if (!configs.isEmpty()) {
cachedConfig = configs[0];
return cachedConfig;
}
// Fallback defaults -- ensures functionality even without deployed CMDT
AI_Config__mdt fallback = new AI_Config__mdt();
fallback.Model_API_Name__c = 'claude-sonnet-4-5-20250929';
fallback.Max_Response_Tokens__c = 1024;
fallback.Temperature__c = 0.7;
fallback.Chatbot_Name__c = 'Assistant';
fallback.Monthly_Token_Budget__c = 500000;
cachedConfig = fallback;
return cachedConfig;
}
}
Loading Context Records by Category
For behavior rules stored across multiple CMDT records, query by category and sort order to assemble the system prompt dynamically:
public static String buildSystemPrompt() {
List<AI_Context__mdt> contexts = [
SELECT Category__c, Context_Text__c
FROM AI_Context__mdt
WHERE Is_Active__c = true
ORDER BY Category__c, Sort_Order__c
];
String prompt = '';
String currentCategory = '';
for (AI_Context__mdt ctx : contexts) {
if (ctx.Category__c != currentCategory) {
prompt += '\n\n' + ctx.Category__c.toUpperCase() + ':\n';
currentCategory = ctx.Category__c;
}
prompt += ctx.Context_Text__c + '\n';
}
return prompt.trim();
}
Multi-Feature Configuration
When multiple features share the same CMDT structure but need different values, use the DeveloperName field as a feature key:
// Portal chatbot
AI_Config__mdt portalConfig = getConfigByName('Portal_Chatbot');
// Internal support assistant
AI_Config__mdt internalConfig = getConfigByName('Internal_Assistant');
Each feature gets its own CMDT record with distinct model, temperature, and token settings -- all managed through Salesforce Setup without code changes.
Testing Considerations
CMDTs are automatically available in test context without @TestSetup or @IsTest(SeeAllData=true). If your code uses the fallback pattern above, tests pass even in scratch orgs where CMDT records have not been deployed. This makes the fallback approach especially valuable for CI/CD pipelines running tests against clean environments.