Skip to content
Salesforce 16 min read 11 sections

Salesforce Custom Metadata vs Custom Settings vs Custom Labels — Full Comparison

Compare Salesforce Custom Metadata vs Custom Settings. Learn differences, use cases, and best practices for real-world projects.

RL

RizeX Labs

RizeX Labs

Published

Last updated

Related programme

Salesforce Training

Admin, Apex, Lightning Web Components and integrations in a live batch, with an industry project, mock interviews and a network of 270 hiring companies.

See the curriculum

3000+ learners · 270+ hiring partners

Introduction: The Configuration Conundrum

ESalesforce Custom Metadata vs Custom Settings is not just a technical comparison—it’s a decision that directly impacts how scalable, maintainable, and deployment-ready your entire Salesforce architecture will be. Most developers underestimate this choice early on, treating configuration storage as a minor detail. That’s a mistake. The wrong approach here doesn’t fail immediately—it creates slow, painful problems over time.

salesforce custom metadata vs custom settings

At some point, every Salesforce architect faces the same challenge: where should configuration data live? Whether it's feature toggles, business rules, integration endpoints, or environment-specific values, this data controls how your application behaves. Choosing between Custom Settings, Custom Metadata Types, and Custom Labels isn’t about preference—it’s about understanding trade-offs at a deeper level.

Custom Settings were once the default solution. They offer fast access and simplicity, but they lack modern deployment flexibility and can create issues in multi-environment setups. Custom Labels, while useful, are often misused by developers trying to avoid proper design—they are meant strictly for text and translation, not for application logic or structured configuration.

Custom Metadata Types represent Salesforce’s evolution in configuration management. They are deployable, package-friendly, and designed for long-term scalability. But even they are not perfect—limitations like restricted runtime updates can become blockers if you don’t plan correctly.

This comparison goes beyond surface-level definitions. We’ll break down how each option behaves in real-world scenarios, how they interact with governor limits, and how they impact CI/CD pipelines. Because if you’re still choosing based on convenience instead of architecture, you’re not building a system—you’re building future problems.

Understanding Custom Settings

Technical Architecture

Custom Settings are essentially custom objects with special characteristics that allow application-wide or profile/user-specific data storage. Salesforce stores Custom Settings data in the application cache, making retrieval faster than standard SOQL queries against custom objects.

Custom Settings come in two flavors:

1. List Custom Settings: Store reusable, static data accessible across your organization regardless of user or profile.

2. Hierarchy Custom Settings: Provide organization-wide defaults with the ability to override at profile or user level, following a hierarchy: Organization → Profile → User.

Technical Implementation

Here's a practical example implementing an API integration configuration using Custom Settings:

apex// Custom Setting: API_Configuration__c (Hierarchy)
// Fields: Endpoint__c, Timeout__c, Retry_Count__c, API_Key__c

public class APIConfigurationManager {
    
    // Retrieves hierarchical custom setting with caching
    public static API_Configuration__c getConfig() {
        API_Configuration__c config = API_Configuration__c.getInstance();
        
        if (config == null || config.Endpoint__c == null) {
            throw new ConfigurationException('API Configuration not found for current context');
        }
        
        return config;
    }
    
    // Example usage in integration class
    public static HttpResponse callExternalAPI(String payload) {
        API_Configuration__c config = getConfig();
        
        HttpRequest req = new HttpRequest();
        req.setEndpoint(config.Endpoint__c);
        req.setTimeout(Integer.valueOf(config.Timeout__c));
        req.setMethod('POST');
        req.setBody(payload);
        req.setHeader('Authorization', 'Bearer ' + config.API_Key__c);
        
        Http http = new Http();
        HttpResponse response;
        Integer retryCount = 0;
        
        do {
            try {
                response = http.send(req);
                if (response.getStatusCode() == 200) {
                    break;
                }
            } catch (Exception e) {
                retryCount++;
                if (retryCount >= config.Retry_Count__c) {
                    throw e;
                }
            }
        } while (retryCount < config.Retry_Count__c);
        
        return response;
    }
}

Governor Limits and Performance

Custom Settings don't count against SOQL query limits when using getInstance() or getValues() methods, as they're retrieved from the application cache. However:

  • Maximum 300 Custom Settings per org
  • List Custom Settings limited to 10 MB per org
  • Each Custom Settings record counts against the 10 MB limit
  • Hierarchy Custom Settings are subject to standard data storage limits

Deployment Characteristics

Custom Settings data requires manual configuration across environments. The structure (fields) deploys via metadata, but the data itself doesn't. This creates friction in CI/CD pipelines:

XML<!-- CustomSettings metadata deploys, but not the data -->
<customObject xmlns="http://soap.sforce.com/2006/04/metadata">
    <customSettingsType>Hierarchy</customSettingsType>
    <enableFeeds>false</enableFeeds>
    <label>API Configuration</label>
    <!-- Fields deploy here -->
</customObject>

You'll need to write post-deployment scripts or use tools like the Salesforce CLI data import/export to manage Custom Settings data across sandboxes and production.

Understanding Custom Metadata Types

Technical Architecture

Custom Metadata Types (CMDTs) represent Salesforce's modern approach to configuration management. Unlike Custom Settings, CMDTs are true metadata—their records deploy alongside your code through change sets, packages, or CI/CD pipelines.

Architecturally, CMDTs provide:

  • Full metadata API support
  • Declarative relationships to other metadata (Entity Definitions, Field Definitions)
  • Support for record types, validation rules, and protected custom metadata within managed packages
  • Transactional safety in deployments

Technical Implementation

Here's a sophisticated example using Custom Metadata Types for a multi-channel notification routing system:

apex// Custom Metadata Type: Notification_Channel__mdt
// Fields: Channel_Name__c, Handler_Class__c, Priority__c, Active__c, 
//         Retry_Logic__c, Max_Retries__c

public class NotificationRouter {
    
    private static Map<String, Notification_Channel__mdt> channelConfigCache;
    
    // Load all active channels with efficient caching
    static {
        channelConfigCache = new Map<String, Notification_Channel__mdt>();
        
        for (Notification_Channel__mdt channel : [
            SELECT Channel_Name__c, Handler_Class__c, Priority__c, 
                   Retry_Logic__c, Max_Retries__c, Active__c
            FROM Notification_Channel__mdt
            WHERE Active__c = true
            ORDER BY Priority__c ASC
        ]) {
            channelConfigCache.put(channel.Channel_Name__c, channel);
        }
    }
    
    public static void sendNotification(String channelName, String message, Map<String, Object> params) {
        if (!channelConfigCache.containsKey(channelName)) {
            throw new ChannelNotFoundException('Channel not configured: ' + channelName);
        }
        
        Notification_Channel__mdt config = channelConfigCache.get(channelName);
        
        // Dynamic handler instantiation based on metadata
        Type handlerType = Type.forName(config.Handler_Class__c);
        if (handlerType == null) {
            throw new ConfigurationException('Handler class not found: ' + config.Handler_Class__c);
        }
        
        INotificationHandler handler = (INotificationHandler)handlerType.newInstance();
        
        // Execute with retry logic from metadata
        executeWithRetry(handler, message, params, config);
    }
    
    private static void executeWithRetry(INotificationHandler handler, String message, 
                                        Map<String, Object> params, 
                                        Notification_Channel__mdt config) {
        Integer attempts = 0;
        Boolean success = false;
        
        while (attempts < config.Max_Retries__c && !success) {
            try {
                handler.send(message, params);
                success = true;
            } catch (Exception e) {
                attempts++;
                if (attempts >= config.Max_Retries__c) {
                    // Log final failure
                    System.debug(LoggingLevel.ERROR, 
                        'Notification failed after ' + attempts + ' attempts: ' + e.getMessage());
                    throw e;
                }
                // Implement backoff strategy from metadata
                if (config.Retry_Logic__c == 'Exponential') {
                    // Wait logic here (in async context)
                }
            }
        }
    }
    
    // Interface for handler implementations
    public interface INotificationHandler {
        void send(String message, Map<String, Object> params);
    }
}

// Example handler implementation
public class EmailNotificationHandler implements NotificationRouter.INotificationHandler {
    public void send(String message, Map<String, Object> params) {
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        email.setToAddresses(new List<String>{(String)params.get('recipient')});
        email.setSubject((String)params.get('subject'));
        email.setPlainTextBody(message);
        Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{email});
    }
}

Metadata Relationships and Advanced Features

Custom Metadata Types support relationships to standard and custom objects, as well as other metadata types:

apex// Custom Metadata Type: Field_Validation_Rule__mdt
// Fields: Object_API_Name__c (EntityDefinition), Field_API_Name__c (FieldDefinition),
//         Validation_Type__c, Error_Message__c

public class DynamicFieldValidator {
    
    public static void validateRecord(SObject record) {
        String objectName = record.getSObjectType().getDescribe().getName();
        
        List<Field_Validation_Rule__mdt> rules = [
            SELECT Field_API_Name__c, Validation_Type__c, Error_Message__c,
                   Min_Value__c, Max_Value__c, Regex_Pattern__c
            FROM Field_Validation_Rule__mdt
            WHERE Object_API_Name__c = :objectName
            AND Active__c = true
        ];
        
        for (Field_Validation_Rule__mdt rule : rules) {
            Object fieldValue = record.get(rule.Field_API_Name__c);
            
            switch on rule.Validation_Type__c {
                when 'Range' {
                    validateRange(fieldValue, rule);
                }
                when 'Regex' {
                    validatePattern(fieldValue, rule);
                }
                when 'Required' {
                    validateRequired(fieldValue, rule);
                }
            }
        }
    }
    
    private static void validateRange(Object value, Field_Validation_Rule__mdt rule) {
        if (value == null) return;
        
        Decimal numValue = Decimal.valueOf(String.valueOf(value));
        if (numValue < rule.Min_Value__c || numValue > rule.Max_Value__c) {
            throw new ValidationException(rule.Error_Message__c);
        }
    }
    
    private static void validatePattern(Object value, Field_Validation_Rule__mdt rule) {
        if (value == null) return;
        
        String stringValue = String.valueOf(value);
        Pattern p = Pattern.compile(rule.Regex_Pattern__c);
        if (!p.matcher(stringValue).matches()) {
            throw new ValidationException(rule.Error_Message__c);
        }
    }
    
    private static void validateRequired(Object value, Field_Validation_Rule__mdt rule) {
        if (value == null || String.valueOf(value).trim() == '') {
            throw new ValidationException(rule.Error_Message__c);
        }
    }
    
    public class ValidationException extends Exception {}
}

Governor Limits and Performance

Custom Metadata Types are queried using SOQL, which means:

  • They count against the 100 SOQL query limit per transaction
  • Each query returns up to 50,000 records
  • Maximum 100 Custom Metadata Types per org
  • Maximum 300 fields per Custom Metadata Type
  • Records are cached automatically by Salesforce's platform cache

Critical performance consideration: When querying Custom Metadata Types, leverage static initialization blocks for application-wide configuration to minimize SOQL queries:

apexpublic class ConfigurationCache {
    
    public static final Map<String, Integration_Config__mdt> INTEGRATION_CONFIGS {
        get {
            if (INTEGRATION_CONFIGS == null) {
                INTEGRATION_CONFIGS = new Map<String, Integration_Config__mdt>();
                for (Integration_Config__mdt config : Integration_Config__mdt.getAll().values()) {
                    INTEGRATION_CONFIGS.put(config.DeveloperName, config);
                }
            }
            return INTEGRATION_CONFIGS;
        }
        private set;
    }
}

Deployment Characteristics

Custom Metadata Types deploy as true metadata, making them ideal for CI/CD pipelines:

XML<!-- Both structure and data deploy together -->
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>Production API Config</label>
    <protected>false</protected>
    <values>
        <field>Endpoint__c</field>
        <value>https://api.production.example.com</value>
    </values>
    <values>
        <field>Timeout__c</field>
        <value>30000</value>
    </values>
</CustomMetadata>

Understanding Custom Labels

Technical Architecture

Custom Labels are text values stored as metadata specifically designed for:

  • Multi-language support (internationalization/i18n)
  • Externalization of user-facing text
  • Centralized message management
  • Dynamic text injection in Visualforce, Lightning, and Apex

Custom Labels support translation workbench integration and can reference merge fields.

Technical Implementation

apex// Custom Label Examples: API_Error_Message, Record_Save_Success, Validation_Error_Pattern

public class UserFacingMessages {
    
    // Using custom labels for error handling
    public static void processRecord(Account acc) {
        try {
            validateAccount(acc);
            upsert acc;
            
            // Success message with merge field
            String successMsg = String.format(
                System.Label.Record_Save_Success,
                new List<String>{acc.Name, String.valueOf(acc.Id)}
            );
            ApexPages.addMessage(new ApexPages.Message(
                ApexPages.Severity.CONFIRM, 
                successMsg
            ));
            
        } catch (DmlException e) {
            // Error message from custom label
            ApexPages.addMessage(new ApexPages.Message(
                ApexPages.Severity.ERROR,
                System.Label.API_Error_Message + ': ' + e.getMessage()
            ));
        }
    }
    
    private static void validateAccount(Account acc) {
        // Using custom label for validation pattern
        Pattern emailPattern = Pattern.compile(System.Label.Email_Validation_Pattern);
        
        if (!emailPattern.matcher(acc.PersonEmail).matches()) {
            throw new ValidationException(
                String.format(System.Label.Validation_Error_Pattern, 
                    new List<String>{'Email', acc.PersonEmail})
            );
        }
    }
    
    // Lightning Component usage
    @AuraEnabled
    public static String getLocalizedMessage(String labelName) {
        // Dynamic label access (use cautiously - prefer direct reference)
        return System.Label.get('', labelName, UserInfo.getLanguage());
    }
}

Governor Limits and Characteristics

  • Maximum 5,000 Custom Labels per org
  • Maximum 1,000 characters per label
  • Labels are cached and don't count against SOQL limits
  • Support up to 127 languages via Translation Workbench
  • Labels deploy as metadata but are read-only at runtime

When NOT to Use Custom Labels

Common anti-pattern: Using Custom Labels for application logic configuration:

apex// ❌ ANTI-PATTERN: Don't do this
public static Integer getRetryCount() {
    return Integer.valueOf(System.Label.API_Retry_Count); // Wrong!
}

// ✅ CORRECT: Use Custom Metadata or Custom Settings
public static Integer getRetryCount() {
    return Integer.valueOf(API_Config__mdt.getInstance('Default').Retry_Count__c);
}

Comprehensive Comparison Table

FeatureCustom SettingsCustom Metadata TypesCustom Labels
Primary Use CaseUser/profile-specific configApplication config & business rulesUI text & internationalization
Deploys as MetadataStructure onlyStructure + dataYes (text content)
SOQL RequiredNo (cached via getInstance())Yes (but cached by platform)No (accessed via System.Label)
Counts Against SOQL LimitsNoYesNo
Supports HierarchyYes (Hierarchy type)NoNo
Package SupportLimitedFull (protected metadata)Full
Record RelationshipsNoYes (to metadata & objects)No
Data VisibilityAll users can queryAll users can queryAll users can access
Runtime ModificationYes (via DML/API)No (metadata API only)No
Multi-Language SupportNoNoYes (127 languages)
Maximum Records~300 settings with data limitsUp to 50,000 records per type5,000 labels
Validation RulesNoYesNo
CI/CD FriendlyPartial (structure only)ExcellentExcellent
Test Data CreationEasy (DML in tests)Requires setMock() or test recordsNot needed (always available)
PerformanceExcellent (application cache)Good (platform cache + SOQL)Excellent (application cache)
Field Types SupportedStandard field typesStandard field types + metadata relationshipsText only
Security ModelProfile/user hierarchySame as other metadataSame as other metadata

Decision-Making Guidelines: When to Use What

salesforce custom metadata vs custom settings

Use Custom Settings When:

  1. You need user or profile-specific configuration overrides
    • Example: Territory-based discount percentages that vary by sales rep
    • Example: Feature flags that enable different capabilities by profile
  2. Configuration needs runtime modification via UI
    • Example: Admin-managed thresholds in a custom app
    • Example: Temporary feature toggles changed frequently
  3. You're working with legacy code that already uses them
    • Refactoring existing Custom Settings to CMDTs requires cost-benefit analysis
  4. You need extremely fast reads without SOQL queries
    • Custom Settings' getInstance() is marginally faster than CMDT queries

Use Custom Metadata Types When:

  1. Configuration must deploy with code (modern best practice)
    • Example: Integration endpoints that differ by environment
    • Example: Validation rules that should match application version
  2. You need relationships to other metadata or objects
    • Example: Field mappings between objects
    • Example: Dynamic routing based on record types
  3. Building managed packages requiring protected configuration
    • Example: ISV apps with customer-customizable behavior
  4. Implementing plugin/strategy patterns
    • Example: Payment gateway configurations with handler classes
    • Example: Notification channels with different implementations
  5. You want comprehensive test coverage without test data dependencies
    • Use Test.createStub() for Custom Metadata in tests
  6. Creating SaaS-style multi-tenant configurations
    • Example: Feature matrix for different subscription tiers

Use Custom Labels When:

  1. Storing user-facing text requiring translation
    • Example: Button labels, error messages, help text
    • Example: Email templates with merge fields
  2. You need centralized message management
    • Example: Consistent terminology across components
    • Example: Brand-specific messaging
  3. Supporting multiple languages is a requirement
    • Custom Labels are the only native i18n solution
  4. Externalizing text from code for non-technical stakeholders
    • Example: Marketing team managing promotional messages
    • Example: Legal team controlling disclaimer text

Use None of These When:

  • Large datasets: Use Custom Objects or Big Objects
  • Transactional data: Use Standard/Custom Objects
  • Sensitive secrets: Use Named Credentials or Protected Custom Settings
  • Complex relationships: Use Custom Objects with proper architecture

Common Mistakes and Anti-Patterns

Mistake 1: Using Custom Labels for Logic

apex// ❌ WRONG
if (opportunityStage == System.Label.Closed_Won_Stage) {
    // Logic depends on user-editable text
}

// ✅ CORRECT
if (opportunityStage == Opportunity_Config__mdt.getInstance('Default').Closed_Won_Stage__c) {
    // Logic uses typed configuration
}

Mistake 2: Not Caching Custom Metadata Queries

apex// ❌ INEFFICIENT
public void processRecords(List<Account> accounts) {
    for (Account acc : accounts) {
        // This queries CMDT on every iteration!
        Integration_Config__mdt config = [SELECT Endpoint__c FROM Integration_Config__mdt LIMIT 1];
    }
}

// ✅ EFFICIENT
public void processRecords(List<Account> accounts) {
    Integration_Config__mdt config = [SELECT Endpoint__c FROM Integration_Config__mdt LIMIT 1];
    for (Account acc : accounts) {
        // Reuse cached config
    }
}

Mistake 3: Overusing Hierarchy Custom Settings

apex// ❌ COMPLEXITY TRAP
// Having 20+ hierarchy settings creates maintenance nightmare
API_Config__c.getInstance().Endpoint__c
API_Config__c.getInstance().Timeout__c
Feature_Flags__c.getInstance().Enable_Feature_A__c
Feature_Flags__c.getInstance().Enable_Feature_B__c
// ... etc

// ✅ BETTER APPROACH
// Use Custom Metadata with selective hierarchy where needed
Integration_Configs__mdt configs = Integration_Configs__mdt.getAll();

Mistake 4: Mixing Configuration Strategies

Maintain consistency—don't use Custom Settings for API config in one class and Custom Metadata in another for the same logical domain.

Best Practices for Salesforce Configuration

1. Establish Configuration Strategy Early

Document your org's configuration standards:

  • Custom Metadata Types: Default for app configuration
  • Custom Settings: Only for user/profile-specific overrides
  • Custom Labels: Exclusively for UI text

2. Use Descriptive Naming Conventions

apex// Good naming makes purpose clear
Email_Service_Config__mdt          // Configuration
Feature_Access__c                   // Hierarchy Custom Setting (note __c)
Err_InvalidEmailFormat             // Custom Label (prefix for category)

3. Implement Configuration Validation

apexpublic class ConfigValidator {
    
    public static void validateIntegrationConfigs() {
        for (Integration_Config__mdt config : Integration_Config__mdt.getAll().values()) {
            if (String.isBlank(config.Endpoint__c)) {
                throw new ConfigException('Invalid config: ' + config.DeveloperName);
            }
            
            if (!config.Endpoint__c.startsWith('https://')) {
                throw new ConfigException('Endpoint must use HTTPS: ' + config.DeveloperName);
            }
        }
    }
}

4. Version Control Everything

Treat configuration as code:

  • Custom Metadata records in source control
  • Custom Settings structure in source control
  • Data migration scripts for Custom Settings data
  • Custom Labels tracked with translations

5. Test Configuration Dependencies

apex@IsTest
private class NotificationRouterTest {
    
    @IsTest
    static void testWithMockMetadata() {
        // Create stub for Custom Metadata
        Notification_Channel__mdt mockChannel = new Notification_Channel__mdt(
            Channel_Name__c = 'Email',
            Handler_Class__c = 'EmailNotificationHandler',
            Max_Retries__c = 3
        );
        
        // Test logic with mocked config
        // Implementation depends on abstraction layer
    }
}

6. Document Configuration Dependencies

Maintain a configuration map showing:

  • Which classes depend on which configuration
  • Environment-specific values
  • Migration paths between configuration types

Migration Strategies

Migrating from Custom Settings to Custom Metadata

apex// 1. Export Custom Settings data
List<API_Config__c> settings = API_Config__c.getAll().values();

// 2. Transform to Custom Metadata format (manual process)
// Create corresponding CMDT records via Metadata API or Setup UI

// 3. Update code incrementally
// Old: API_Config__c.getInstance().Endpoint__c
// New: API_Config__mdt.getInstance('Default').Endpoint__c

// 4. Deploy and test in sandbox
// 5. Migrate production
// 6. Deprecate Custom Settings after validation period

Conclusion

The choice between Salesforce Custom Metadata vs Custom Settings vs Custom Labels fundamentally impacts your application's architecture, deployability, and maintainability.

Modern best practice strongly favors Custom Metadata Types for application configuration due to their superior deployment characteristics and metadata-first approach. Custom Settings remain valuable for profile-specific overrides and rapid administrative changes. Custom Labels exclusively serve internationalization needs.

The key is architectural discipline: establish clear conventions early, document your decisions, and resist the temptation to mix approaches without justification. As Salesforce continues evolving toward metadata-driven development, Custom Metadata Types increasingly represent the path forward for scalable, enterprise-grade configuration management.

Your configuration strategy isn't just a technical decision—it's a statement about your organization's commitment to DevOps maturity, maintainability, and long-term platform health. Choose wisely, implement consistently, and your future self (and teammates) will thank you.

About RizeX Labs

At RizeX Labs, we specialize in delivering scalable and efficient Salesforce solutions that solve real business problems—not theoretical ones. Our expertise spans across platform customization, automation, and architecture design using tools like Custom Metadata, Custom Settings, and Custom Labels.

We focus on building maintainable and deployment-friendly systems that reduce technical debt and improve long-term performance. Instead of quick fixes, we implement solutions that scale with your business.

Internal Links:


External Links:

Questions we get asked

1. What is the main difference between Custom Metadata and Custom Settings?
Custom Metadata Types deploy as metadata (both structure and data), making them ideal for CI/CD pipelines. Custom Settings only deploy their structure as metadata—the data must be manually configured in each environment. Custom Metadata is the modern approach for application configuration, while Custom Settings excel at user/profile-specific overrides.
2. Do Custom Metadata queries count against SOQL governor limits?
Yes, Custom Metadata queries count against the 100 SOQL query limit per transaction. However, Salesforce automatically caches these queries at the platform level. To optimize, use static initialization blocks or getAll() method to load all records once and cache them in your code.
3. When should I use Custom Settings instead of Custom Metadata?
Use Custom Settings when you need: Profile or user-specific configuration hierarchies Runtime modification of configuration values through the UI Extremely fast reads without SOQL queries (using getInstance()) Backward compatibility with legacy code For modern development, prefer Custom Metadata Types for most configuration needs.
4. Can I use Custom Labels for storing configuration values like API endpoints?
No, this is an anti-pattern. Custom Labels should only store user-facing text for internationalization. Using them for configuration logic creates maintenance issues and makes your code fragile. Use Custom Metadata Types or Custom Settings for configuration values—they support proper data types and validation.
5. How do I test code that uses Custom Metadata Types?
You have three options: Create actual Custom Metadata records in your org (deploy with your code) Use Test.createStub() to mock Custom Metadata objects in tests Create a wrapper/service class that you can mock using dependency injection The first approach is simplest for most cases since Custom Metadata deploys with your test code.
6. What are the maximum limits for each configuration type?
Custom Settings: 300 Custom Settings per org, 10 MB total for List Custom Settings Custom Metadata Types: 100 types per org, 50,000 records per type, 300 fields per type Custom Labels: 5,000 labels per org, 1,000 characters per label These limits make Custom Metadata Types the most scalable option for large configuration datasets.
7. Can Custom Metadata Types have relationships like standard objects?
Yes, Custom Metadata Types support: Metadata relationships (to EntityDefinition, FieldDefinition, etc.) Lookup relationships to other Custom Metadata Types Relationships to standard objects (though records can't be created via DML) This makes them ideal for complex configuration scenarios like field mappings and dynamic routing rules.
8. How do Custom Settings hierarchy levels work?
Hierarchy Custom Settings follow this precedence order: User level (highest priority) Profile level Organization default (lowest priority) When you call getInstance(), Salesforce returns the most specific value available. This allows org-wide defaults with selective overrides for specific profiles or users.
9. Which configuration type is best for managed packages?
Custom Metadata Types are the best choice for managed packages because they support: Protected custom metadata (subscribers can't see sensitive values) Deployable configuration that installs automatically Subscriber customization of specific fields while protecting others Proper versioning and upgrade paths Custom Settings can work but lack the protected metadata capability
10. Should I migrate existing Custom Settings to Custom Metadata?
Evaluate based on these factors: Migrate if: You have deployment pain, need relationships, building packages, or want modern architecture Keep if: You actively use hierarchy features, need runtime admin modification, or migration cost exceeds benefits Hybrid approach: Keep user-specific Custom Settings, migrate application config to Custom Metadata
Last updated 21 September 2026