Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Deployment

Cannot delete this <component>: it is referenced by other components

You're trying to delete or refactor a metadata component (custom field, Apex class, page, flow, etc.) that other metadata still references. The deployment names which components depend on it — that's where you have to refactor before the destructive change can land.

Also seen asdependent components·Cannot delete this·is referenced by other components·Cannot delete custom field referenced by

The release engineer prepared a destructive change to remove three deprecated custom fields and a Visualforce page nobody uses anymore. The pre-deploy validation ran clean. The production deploy failed at the destructive step with "Cannot delete this Visualforce page: it is referenced by other components." A nested list showed five custom buttons, two Apex classes, and a managed package extension all pointing at the page. The release window closed before the team could untangle which references were live and which were stale.

What the platform is actually checking

When a Salesforce deploy tries to delete a metadata component, the platform walks the dependency graph for that component and refuses to delete it if any other component still references it. The check is conservative: any reference counts, even if the referencing component is itself broken or inactive.

Dependencies live in many places. Apex classes can reference custom fields, custom objects, custom labels, and Visualforce pages. Visualforce pages reference custom controllers, components, and Apex classes. Flows reference fields, objects, and other flows. Custom buttons reference pages, classes, and URLs. Lightning components reference fields and Apex methods. Reports reference fields. List views reference fields. Approval processes reference fields and email templates. The graph touches every layer of the metadata model.

The platform doesn't tell you which reference is live and which is dead. A custom field referenced only by an inactive Process Builder still counts as a dependency. A page referenced by a deleted Lightning component (where the deletion is sitting in the same destructive change) still counts until the deletion completes.

The conservative behavior is a feature. It prevents accidental data loss and broken pages. It's also the source of most "I just want to delete one thing" frustration.

The broken example

A destructiveChanges.xml that tries to remove a Visualforce page used by a deprecated workflow:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>OpportunityProposalGenerator</members>
        <name>ApexPage</name>
    </types>
    <version>60.0</version>
</Package>

The deploy runs. Validation passes (because the package itself is well-formed). The destructive step fails:

Cannot delete this Visualforce page: it is referenced by other components.
References:
  - Custom Button: GenerateProposal (on Opportunity)
  - Apex Class: ProposalController
  - Apex Class: ProposalControllerTest
  - Custom Button: ResendProposal (on Quote)
  - Email Template: ProposalEmail (HTML body references the page url)

Five references. The release engineer doesn't know which ones to keep removing and which ones to remove first. The Apex class ProposalController is the page's controller, so it has to go with the page. The custom buttons need their action changed before the page can be removed. The email template references the page in its HTML body, which is metadata the platform also tracks as a dependency.

A second shape: deleting a custom field where a formula on a different object references the field via a relationship. The reference goes through a master-detail or lookup, which makes the dependency invisible in the source object's Setup view but very visible to the metadata API at delete time.

A third shape: deleting an Apex class where another class extends it. The base class can't be removed before the subclass.

What "destructive change" actually does

A destructive change is a metadata API operation that deletes components. The deploy specifies the deletes in destructiveChanges.xml (alongside any additions in package.xml). The platform processes deletes after additions in a single deploy, which lets you replace a class with a different class atomically.

The deletes happen in a single transaction from the API perspective. If any delete fails, the whole destructive step rolls back (some metadata types have nuances, but the conservative reading is: assume the whole step is atomic).

The dependency check happens at the moment of delete. The platform doesn't pre-validate; it tries the delete, hits the dependency, and fails. This is why a deploy can validate clean and then fail at execution. Validation tests that the metadata is well-formed; the actual delete tests that nothing depends on the deleted component.

The fix, ordered by likelihood

Remove the references first, then delete the component in a follow-up deploy. The cleanest path. Ship two deploys: the first removes the references (changes the custom buttons to point at a different page, updates the email template, ships modified Apex classes), the second deletes the page now that nothing points at it. The two-deploy cadence is annoying but safe; each step is small and reversible.

Bundle the references and the delete into one destructive change. If the references are themselves things you want to delete, include them in the same destructiveChanges.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>GenerateProposal</members>
        <members>ResendProposal</members>
        <name>WebLink</name>
    </types>
    <types>
        <members>ProposalController</members>
        <members>ProposalControllerTest</members>
        <name>ApexClass</name>
    </types>
    <types>
        <members>OpportunityProposalGenerator</members>
        <name>ApexPage</name>
    </types>
    <version>60.0</version>
</Package>

The platform deletes in an order that satisfies the dependency graph: the buttons and email template lose their reference when they're deleted, the Apex classes lose their reference when they're deleted, and finally the page can be removed because nothing points at it. One deploy, one transaction.

Use the Where Is This Used? feature to find all references. Setup, object manager, your component, then click Where Is This Used. The view shows everything that references the component, including managed-package extensions, validation rules, and report types. Use the list as the working set for your destructive change.

The feature has limits. Some reference types aren't reported (formula fields cross-referenced via lookups, soft references in custom labels). Always pair the UI list with the deploy error's nested reference list.

The fixed example

A two-deploy cadence that cleanly removes the page:

<!-- Deploy 1: package.xml updates buttons + email template + controller -->
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>GenerateProposal</members>
        <members>ResendProposal</members>
        <name>WebLink</name>
    </types>
    <types>
        <members>ProposalEmail</members>
        <name>EmailTemplate</name>
    </types>
    <types>
        <members>ProposalController</members>
        <name>ApexClass</name>
    </types>
    <version>60.0</version>
</Package>

The buttons get changed to use a new URL or point at a new page. The email template's HTML body is updated to remove the old page link. The ProposalController is rewritten to no longer expect the old page.

<!-- Deploy 2: destructiveChanges.xml removes the page after Deploy 1 ships -->
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>OpportunityProposalGenerator</members>
        <name>ApexPage</name>
    </types>
    <version>60.0</version>
</Package>

The page has no references after Deploy 1 ships. Deploy 2 removes it cleanly. Each deploy is small, focused, and easy to roll back individually.

When the reference is in a managed package

A managed package can declare a dependency on your custom field, page, or class via its package definition. If a managed extension package references the component you want to delete, your destructive change fails until the package is uninstalled or upgraded to a version that no longer references the component.

The remediation path:

  1. Identify the managed package by the reference's namespace prefix in the error message.
  2. Contact the package vendor and request a version that removes the dependency, or check their release notes for a planned removal.
  3. While waiting, work around the dependency by leaving the component in place and marking it deprecated in the field's description. Filter it out of layouts and disable any functionality that uses it.

Sometimes the vendor's response time is measured in quarters. Plan for the dependency to persist longer than you'd like.

Soft references that hurt at delete time

Some references aren't captured in the platform's static graph. Common examples:

A custom label referenced by Apex code via System.Label.MyLabel. If you delete the label, the Apex code still compiles in the deploy validation (the validation might not catch every label reference) but fails at runtime when the label is read.

A field referenced in a report's filter or a list view's filter. The platform tracks these in some cases but not always, especially for older list views or reports authored before the dependency tracking improved.

A field referenced in a custom-metadata-type record's text field. The platform doesn't know that the text content references the field name; it just sees a string.

A field referenced in a Visualforce page's binding {!record.Field__c}. This one is usually tracked, but Visualforce evaluated at runtime via dynamic property access (Schema.SObjectField sof = Schema.getGlobalDescribe().get('Account').getDescribe().fields.getMap().get('CustomField__c')) bypasses the static graph.

The deploy can succeed in removing the field, then production breaks at runtime. Audit each soft-reference type when you delete heavily-used components.

Closely related deploy errors

ErrorCause
"Cannot delete this <component>: it is referenced by other components"Dependency on the component blocks delete
"Item not found in your organization"Trying to delete a component that doesn't exist
"INVALID_CROSS_REFERENCE_KEY"DML references a record id that doesn't exist or isn't visible
"DEPENDENT_CLASS_INVALID"A class the deploy depends on failed to compile

All four are about the dependency graph. The first is delete-time. The second is "what you're deleting isn't there." The third is record-id level. The fourth is class-level compilation.

Diagnosing in advance

Before submitting a destructive change to production, run it as a --check-only validation against the target org:

sf project deploy validate \
  --manifest manifest/package.xml \
  --post-destructive-changes destructiveChanges.xml \
  --target-org production

The validation runs the dependency check without committing the delete. Every reference shows up in the validation output. Use the output to plan the cleanup sequence before scheduling the actual deploy.

For developer workflows, run --check-only validations in CI on every pull request that touches a destructive change. The reference list shows up at PR time, not on Friday afternoon at production deploy.

Defensive habits

Plan destructive changes as two-deploy cadences when in doubt. Remove the references first, delete the component second. Each deploy is small enough to review carefully.

Use Setup, Where Is This Used? for every component you intend to delete. Pair the UI list with the deploy validation output to catch references the UI doesn't report.

Mark fields and pages deprecated in their description before deleting. The deprecation period gives downstream consumers time to migrate. The platform respects the deprecation in some UI surfaces (the field becomes harder to add to layouts), which reduces accidental new usage.

Keep an explicit list of soft-reference patterns in your team's runbook. Custom labels, list view filters, report filters, Visualforce dynamic property access. The list lives somewhere your release engineers can reference.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Deployment errors