Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Deployment

Cannot change field type from <type1> to <type2> via the API

Salesforce restricts which field type changes are allowed in a deploy. Many type transitions (Number to Text, Picklist to Lookup, Text to Long Text Area, etc.) require a UI-based wizard with data migration. The deploy fails until you make the change via the UI or a destructive-create cycle.

Also seen asCannot change field type·field type cannot be changed·field type change not allowed

A platform team finishes the quarterly cleanup pass on the Opportunity object. One change in the package converts a Text(50) field named Engagement_Tier__c to a Picklist with a controlled value set. The change deploys fine to the sandbox. The production deploy fails with Cannot change field type from Text to Picklist via the API. The release branch already contains forty other changes. The team has to decide whether to back out the field change, restructure the deploy, or stop the release entirely.

What the platform is checking

Salesforce enforces strict rules on field-type changes. Many type transitions are not allowed through the Metadata API at all, because the change requires data migration logic that the API cannot safely perform automatically. A Text to Picklist conversion is one of the most common forbidden transitions: every value in the text column would need to be validated against the new picklist's value set, and rows with values outside the set would either need to be cleared, migrated, or rejected.

The Metadata API can change a field's properties (label, description, required flag, length) but not its data type for most conversions. The allowed conversions are documented per field type. A short list of safe conversions exists (such as Text to Long Text Area, or one numeric type to another with matching precision), but most cross-category changes (Text to Picklist, Text to Number, Number to Date) require manual intervention.

The platform's reasoning is data safety. A type change can corrupt or lose data if performed incorrectly. The API enforces the rule at the metadata layer so a careless deploy cannot silently destroy production data. The fix is always a controlled, multi-step process that includes a data migration plan.

The error fires at deploy time. The Apex Test stage may not run if the validation phase rejects the field change. The deployment status page shows the error against the specific field, and the rest of the deploy is rolled back.

The broken example

The metadata XML for the field change:

<!-- old: force-app/main/default/objects/Opportunity/fields/Engagement_Tier__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Engagement_Tier__c</fullName>
    <label>Engagement Tier</label>
    <length>50</length>
    <required>false</required>
    <type>Text</type>
    <unique>false</unique>
</CustomField>

The branch updates the same file to:

<!-- new: force-app/main/default/objects/Opportunity/fields/Engagement_Tier__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Engagement_Tier__c</fullName>
    <label>Engagement Tier</label>
    <required>false</required>
    <type>Picklist</type>
    <valueSet>
        <restricted>true</restricted>
        <valueSetDefinition>
            <sorted>false</sorted>
            <value>
                <fullName>Strategic</fullName>
                <default>false</default>
            </value>
            <value>
                <fullName>Growth</fullName>
                <default>false</default>
            </value>
            <value>
                <fullName>Standard</fullName>
                <default>false</default>
            </value>
        </valueSetDefinition>
    </valueSet>
</CustomField>

The deploy fails. The error reads Cannot change field type from Text to Picklist via the API.

A second shape: a field changed from Number(10,0) to Currency(10,0). The Metadata API does not allow this transition either, even though both store decimals. The semantics are different and the platform enforces the boundary.

A third shape: a field changed from Picklist to Multi-Select Picklist. Going from single to multi-select requires reorganizing the underlying storage. The Metadata API rejects the change.

The fix, three paths

Use the two-field rename pattern. The safe approach is to keep the existing field and create a new field with the desired type. The new field gets a slightly different name. Data migrates from the old field to the new field through a one-time data load. After the migration, the old field is deprecated and eventually deleted.

Step 1: Create the new picklist field with a new name:

<!-- force-app/main/default/objects/Opportunity/fields/Engagement_Tier_V2__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Engagement_Tier_V2__c</fullName>
    <label>Engagement Tier</label>
    <required>false</required>
    <type>Picklist</type>
    <valueSet>
        <restricted>true</restricted>
        <valueSetDefinition>
            <sorted>false</sorted>
            <value><fullName>Strategic</fullName><default>false</default></value>
            <value><fullName>Growth</fullName><default>false</default></value>
            <value><fullName>Standard</fullName><default>false</default></value>
        </valueSetDefinition>
    </valueSet>
</CustomField>

Step 2: Deploy the new field. The deploy now succeeds because no type change is involved.

Step 3: Run an Apex one-time data migration:

public class EngagementTierMigration implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT Id, Engagement_Tier__c, Engagement_Tier_V2__c FROM Opportunity ' +
            'WHERE Engagement_Tier__c != null AND Engagement_Tier_V2__c = null'
        );
    }
    public void execute(Database.BatchableContext bc, List<Opportunity> scope) {
        Set<String> validValues = new Set<String>{'Strategic', 'Growth', 'Standard'};
        for (Opportunity o : scope) {
            String legacy = o.Engagement_Tier__c.trim();
            if (validValues.contains(legacy)) {
                o.Engagement_Tier_V2__c = legacy;
            }
        }
        update scope;
    }
    public void finish(Database.BatchableContext bc) {}
}

Step 4: Update all references (Lightning components, reports, formula fields) to use Engagement_Tier_V2__c.

Step 5: After a few weeks of confirmed parallel operation, deprecate Engagement_Tier__c (mark it not-required, remove from page layouts) and eventually delete it.

Perform the change in the Setup UI for fields that allow it. Some type changes are allowed through the UI but not the API. The Setup UI walks the admin through a conversion wizard that handles data validation interactively. After the change is made manually in each org, the metadata in source control needs to match.

This path requires multiple manual steps across orgs (sandbox, UAT, production) and is brittle. The two-field rename is usually safer.

Reject the change at design time. The team can decide that the type change is not worth the migration cost and keep the field as Text with a validation rule enforcing the allowed values:

<validationRules>
    <fullName>Engagement_Tier_Allowed_Values</fullName>
    <active>true</active>
    <errorConditionFormula>
        AND(
            NOT(ISBLANK(Engagement_Tier__c)),
            NOT(ISPICKVAL(TEXT(Engagement_Tier__c), 'Strategic')),
            NOT(ISPICKVAL(TEXT(Engagement_Tier__c), 'Growth')),
            NOT(ISPICKVAL(TEXT(Engagement_Tier__c), 'Standard'))
        )
    </errorConditionFormula>
    <errorMessage>Engagement Tier must be Strategic, Growth, or Standard</errorMessage>
</validationRules>

The validation rule enforces the set without changing the field type. The UI does not get a picklist dropdown, but the data quality is enforced.

The fixed example

A release plan that handles the field-type change cleanly:

Release 1 (Week 1):
- Add Engagement_Tier_V2__c (Picklist) to source control.
- Deploy to sandbox, UAT, production.
- Verify deploy succeeds (no type change involved).

Release 2 (Week 2):
- Add EngagementTierMigration batch class.
- Deploy to sandbox, UAT, production.
- Run the batch in each org.
- Verify data populated correctly.

Release 3 (Week 3):
- Update all consumers (Lightning components, reports, formula fields)
  to reference Engagement_Tier_V2__c.
- Deploy and verify.

Release 4 (Week 6 or later):
- Mark Engagement_Tier__c as deprecated in source control
  (remove from page layouts, set to Not Required, etc.).
- After a confirmed parallel period, delete the field.

Each release is small, reversible, and independent. The migration is auditable.

Edge case: changes that ARE allowed via the API

The Metadata API supports certain type changes, including:

  • Text to Long Text Area (and certain length increases on Text fields).
  • Email/Phone/URL to Text and back (with constraints).
  • Auto Number prefix changes.
  • Currency precision changes (within limits).

The full allowed-conversions matrix is documented in the Salesforce Metadata API Developer Guide. When in doubt, attempt the change in a sandbox first. If the deploy succeeds, the API allows it.

Edge case: formula fields and their dependents

A formula field cannot have its return type changed. The only way to change the return type is to delete and recreate the formula. Every dependent (reports, dashboards, page layouts, validation rules, other formulas) must be updated to reference the new formula. The two-formula rename pattern (identical to the two-field pattern) handles this safely.

Edge case: managed package fields

Fields in managed packages can be added or modified only by the package developer. Field-type changes in managed packages require a new package version. Consumers of the package have to install the new version to receive the field change. The package developer needs to plan migrations carefully because consumer orgs may not all be on the same data shape.

Edge case: field history tracking

A field with field history tracking enabled may retain history rows referencing the old type. When the field is recreated under a new name, the history is not migrated. If the audit history is critical, document the old field's history before deleting it (export the FieldHistory records via a query).

Edge case: formula and validation rule references

Every formula field, validation rule, workflow rule, flow, process, Apex class, Visualforce page, Lightning component, and report that references the old field needs to be updated to reference the new field. The Salesforce "Where is this used?" feature on the field detail page lists most references. The list is not always complete; Lightning components defined in source control often reference fields by string and do not appear in the Setup-side reference list.

A safe rollout updates the references first to read both fields (Engagement_Tier_V2__c || Engagement_Tier__c), then completes the migration, and finally updates the references to read only the new field. The intermediate form preserves continuity while the data migration runs.

For Apex code, a code search across the repository for the old field name catches references the Setup-side tool misses. The replacement is mechanical: every read of Engagement_Tier__c becomes a read of Engagement_Tier_V2__c.

Edge case: external integrations

External systems that sync data with Salesforce often reference fields by their API names. An integration that reads Engagement_Tier__c will see null after the data migration moves values to Engagement_Tier_V2__c. Coordinate the field rename with the integration team so the consumer-side code updates in the same window as the Salesforce-side rename. A short maintenance window may be needed while both sides switch over.

Defensive habits

Treat field types as effectively permanent. The day you create a field, decide its type carefully because changing it later is expensive.

Use picklists from the start when the value set is known and finite. Use text fields only when the value space is open. Switching later is painful.

Validate metadata changes in a sandbox before opening a pull request. A quick sf project deploy validate flags type-change errors before the change reaches the release branch.

Document the long-term naming convention for V2 fields. A field named Engagement_Tier__c becoming Engagement_Tier_V2__c is acceptable for one cycle; using _V2, _New, or _Picklist consistently keeps the lineage readable.

Test patterns

A test for the migration batch:

@IsTest
static void migrationCopiesValidValuesAndSkipsInvalid() {
    Account a = new Account(Name='Test');
    insert a;

    List<Opportunity> opps = new List<Opportunity>{
        new Opportunity(Name='Op1', AccountId=a.Id, StageName='Prospecting',
                        CloseDate=Date.today().addDays(30), Engagement_Tier__c='Strategic'),
        new Opportunity(Name='Op2', AccountId=a.Id, StageName='Prospecting',
                        CloseDate=Date.today().addDays(30), Engagement_Tier__c='Invalid Value'),
        new Opportunity(Name='Op3', AccountId=a.Id, StageName='Prospecting',
                        CloseDate=Date.today().addDays(30), Engagement_Tier__c=null)
    };
    insert opps;

    Test.startTest();
    Database.executeBatch(new EngagementTierMigration());
    Test.stopTest();

    Map<Id, Opportunity> refreshed = new Map<Id, Opportunity>([
        SELECT Engagement_Tier_V2__c FROM Opportunity WHERE Id IN :opps
    ]);
    System.assertEquals('Strategic', refreshed.get(opps[0].Id).Engagement_Tier_V2__c);
    System.assertEquals(null, refreshed.get(opps[1].Id).Engagement_Tier_V2__c);
    System.assertEquals(null, refreshed.get(opps[2].Id).Engagement_Tier_V2__c);
}

The test confirms valid values migrate, invalid values do not, and null values stay null. Together these cover the migration's contract.

Diagnosing in production

When the deploy fails:

  1. Read the error and identify the specific field and the attempted transition.
  2. Check the Metadata API documentation to confirm whether the transition is allowed.
  3. If not allowed, plan a two-field rename or a Setup-UI conversion.
  4. Update the release branch to include only the steps that are deployable.
  5. Schedule the migration work in subsequent releases.

The diagnosis is usually fast (the error names the field and the types). The recovery is a multi-release plan.

Anti-pattern: editing the metadata XML to "force" the change

Some teams attempt to bypass the validation by editing the field's XML and re-deploying. This does not work; the API enforces the rule regardless of the XML shape. The deploy fails the same way.

Anti-pattern: deleting and re-creating in the same deploy

A delete-then-create plan in a single deploy looks tempting but loses all data and history in the field. A two-step plan with a manual data export between the steps is the only safe form of "delete and recreate."

Quick recovery checklist

  1. Read the error and identify the disallowed transition.
  2. Plan a two-field rename if the type change is genuinely required.
  3. Build a migration batch to move the data.
  4. Update consumers in stages.
  5. Deprecate the old field after a confirmed parallel period.

The class of error has a known playbook. Following the playbook the first time builds the muscle memory for the second.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Deployment errors