Tool to Delete Components from Production

Tool to Delete Components from Production

It is known that we cannot delete components like Apex Classes, Triggers, etc. manually from production. The common approach to delete these components are through IDE or Migration tool. In this article we design a tool to delete these components. We build a Visualforce page which creates two XML files named package.xml and destructiveChanges.xml, later compress them and deploy.

To start with include JSZip as a static resource. Now lets create a Visualforce page as below,


How this works ?

The field “Component Type” contain elements like ApexClass, ApexPage, ApexTrigger, etc and in the field “Component” we have to enter the Object Name of the component to be deleted, click on the button “Add” and the component is added to the wrapper list. When confirmed click on the button “Delete”, a javascript function is triggered to add the wrapper list elements to a destructive changes XML string, package XML string is also initialised, both these strings are converted into files and zipped using JSZip. After this an apex Deploy method is invoked from the Visualforce page javascript, we are using MetadataService Class to make the deployment call.

Please use the below code for the Visualforce page and note that in the controller in the method createService we have to update service endpoint with the URL of the organisation. Also add the organisation url in the Remote site settings.


[sourcecode language=”java” collapse=”true” title=”DeleteComponentsClass.apxc”]
public class DeleteComponentsClass {

// Initializing elements
public String selectedType{get;set;}
public String componentName{get;set;}
public List<DeleteComponentsWrpClass> componentItems{get;set;}

// Getting picklist elements
public static List<SelectOption> getDataTypes(){

List<String> itemTypes = new List<String>{‘ActionLinkGroupTemplate’, ‘AnalyticSnapshot’, ‘ApexClass’, ‘ApexComponent’, ‘ApexPage’,
‘ApexTrigger’, ‘AppMenu’, ‘ApprovalProcess’, ‘AssignmentRules’, ‘AuraDefinitionBundle’, ‘AuthProvider’, ‘AutoResponseRules’, ‘BusinessProcess’,
‘CallCenter’, ‘ChannelLayout’, ‘Community’, ‘CompactLayout’, ‘CorsWhitelistOrigin’, ‘CustomApplication’, ‘CustomField’, ‘CustomMetadata’,
‘CustomObject’, ‘CustomObjectTranslation’, ‘CustomPageWebLink’, ‘CustomPermission’, ‘CustomSite’, ‘CustomTab’, ‘Dashboard’, ‘Document’,
‘EmailTemplate’, ‘EscalationRules’, ‘ExternalDataSource’, ‘FieldSet’, ‘FlexiPage’, ‘FlowDefinition’, ‘Group’, ‘HomePageComponent’, ‘HomePageLayout’,
‘Layout’, ‘Letterhead’, ‘ListView’, ‘MatchingRule’, ‘NamedCredential’, ‘PermissionSet’, ‘PlatformCachePartition’, ‘Portal’, ‘PostTemplate’,
‘Profile’, ‘Queue’, ‘QuickAction’, ‘RecordType’, ‘RemoteSiteSetting’, ‘Report’, ‘ReportType’, ‘Role’, ‘Scontrol’, ‘Settings’, ‘SharingCriteriaRule’,
‘SharingOwnerRule’, ‘SharingReason’, ‘SharingRules’, ‘SharingSet’, ‘SiteDotCom’, ‘StaticResource’, ‘UserPermissions’, ‘ValidationRule’,
‘WebLink’, ‘Workflow’, ‘WorkflowAlert’, ‘WorkflowFieldUpdate’, ‘WorkflowOutboundMessage’, ‘WorkflowRule’, ‘WorkflowTask’};

List<SelectOption> customTypes = new List<SelectOption>();
customTypes.add(new SelectOption(”,’–None–‘));
for(String item : itemTypes){
customTypes.add(new SelectOption(item,item));

return customTypes;

// Adding Items to the List
public void add(){

List<String> splitItems = componentName.split(‘;’);

for(String Item : splitItems){
DeleteComponentsWrpClass compItem = new DeleteComponentsWrpClass();
compItem.compName = Item;
compItem.compType = selectedType;
if(componentItems == null){
componentItems = new List<DeleteComponentsWrpClass>();
componentName = null;


// Deploying zip file
public static String getDeployContent(String zipData){

MetadataService.MetadataPort service = createService();

MetadataService.DeployOptions deployOptions = new MetadataService.DeployOptions();
deployOptions.allowMissingFiles = false;
deployOptions.autoUpdatePackage = false;
deployOptions.checkOnly = false;
deployOptions.ignoreWarnings = false;
deployOptions.performRetrieve = false;
deployOptions.purgeOnDelete = false;
deployOptions.rollbackOnError = true;
deployOptions.testLevel = ‘NoTestRun’;
deployOptions.singlePackage = true;

MetadataService.AsyncResult AsyncResult = service.deploy(zipData, DeployOptions);

return (‘Success’);

// Cancel button action
public PageReference cancel(){

PageReference pageRef = new PageReference(‘/home/home.jsp’);
return pageRef;


// Delete button action
public PageReference deleteItems(){

Long startTime =;
Integer delay = 5000;

do {
// Do nothing
}while ( – startTime < delay);

PageReference pageRef = new PageReference(‘/changemgmt/monitorDeployment.apexp’);
return pageRef;


// Creating service
private static MetadataService.MetadataPort createService() {

MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.endpoint_x = ‘’;
service.timeout_x = 120000;
service.SessionHeader.sessionId = UserInfo.getSessionId();
return service;


// Wrapper Class
public class DeleteComponentsWrpClass {

public String compType{get;set;}
public String compName{get;set;}

public DeleteComponentsWrpClass(){



Visualforce page:

[sourcecode language=”html” collapse=”true” title=”DeleteComponents.vfp”]
<apex:page id=”page” controller=”DeleteComponentsClass”>

<apex:includeScript value=”{!$Resource.jsZip}”/>

<script type=”text/javascript”>
var compMap = {};

function contentToMap(key,value) {
compMap[key] = compMap[key] || [];

function getRemoteContent(){

var keys = Object.keys(compMap);

if(keys.length > 0){
var packageXml = ‘<?xml version=”1.0″ encoding=”UTF-8″?>’ +
‘<Package xmlns=””>’ +
‘<version>35.0</version>’ +
‘</Package>’ ;

var destructiveChanges = ‘<?xml version=”1.0″ encoding=”UTF-8″?>’ +
‘<Package xmlns=””>’ ;

for(var i=0;i<keys.length;i++){

destructiveChanges = destructiveChanges + ‘<types>’;

if(keys[i] == ‘Dashboard’ || keys[i] == ‘Report’ || keys[i] == ‘EmailTemplate’ ||keys[i] == ‘Document’){

for(var j=0;j<compMap[keys[i]].length;j++) {
var items = compMap[keys[i]][j].split(‘/’);
if((destructiveChanges.indexOf(‘<members>’ + items[0] + ‘</members>’) > -1) && (items[0] != ‘unfiled$public’)) {
destructiveChanges = destructiveChanges + ‘<members>’+items[0]+'</members>’;

destructiveChanges = destructiveChanges + ‘<members>’+compMap[keys[i]][j]+'</members>’;

} else {

for(var j=0;j<compMap[keys[i]].length;j++) {
destructiveChanges = destructiveChanges + ‘<members>’+compMap[keys[i]][j]+'</members>’;


destructiveChanges = destructiveChanges + ‘<name>’+keys[i]+'</name>’;
destructiveChanges = destructiveChanges + ‘</types>’;


destructiveChanges = destructiveChanges + ‘</Package>’;

zipFile = new JSZip();
zipFile.generateAsync({type:”base64″}).then(function(data) {

function(result, event){
if (event.status) {

} else {
} else {
alert(‘Add Components’);


<apex:form id=”frm” >

<apex:pageBlock id=”pblck1″ title=”Delete Components”>

<apex:pageBlockSection id=”pbSectn1″ columns=”4″ showHeader=”false”>
<apex:outputLabel value=”Component Type” for=”picklistID” />
<apex:selectList id=”picklistID” value=”{!SelectedType}” title=”Component Type” multiselect=”false” size=”1″>
<apex:selectOptions value=”{!DataTypes}” />

<apex:pageBlockSection id=”pbSectn2″ columns=”4″ showHeader=”false”>
<apex:outputLabel value=”Component” for=”nameID” />
<apex:inputText id=”componentName” value=”{!ComponentName}” />

<apex:pageBlockButtons location=”bottom”>
<apex:commandButton action=”{!add}” value=” Add “> </apex:commandButton>
<apex:commandButton onclick=”getRemoteContent()” action=”{!deleteItems}” value=” Delete “> </apex:commandButton>
<apex:commandButton action=”{!cancel}” value=” Cancel “></apex:commandButton>


<apex:pageBlock id=”pblck2″ >
<table border=’1′>
<tr style=”background:#3498db;color:white”>
<th>Component Name</th>
<th>Component Type</th>
<apex:repeat id=”itemId” value=”{!componentItems}” var=”item”>

<apex:pageBlock >

<apex:pageBlockSection columns=”1″>
<span style=”font-style:Italic”>
Note: Component is the Component Object Name. For Folder components like Dashboard, Document, EmailTemplate and Report, the Folder Name is added as prefix to the Component. For example my Dashboard folder is demoFolder and Dashboard is demoComponent, then the Component name is demoFolder/demoComponent.
You can add more than one Component Name separated by semicolon(‘;’).




Let’s test this:

I have an Apex class “HelloWorld” and Apex Trigger “HelloTrigger”. (I am using the developer org not the production).



Add these components in the tool.


Click on the delete button we are redirected to the Deployment status page displaying record showing deployment succeeded.


We can also check in the Apex classes and Apex Triggers setup that the components are deleted.



This tool can also be used to delete other components like Apex pages, Dashboards, Documents, Reports, Workflows, etc,. The components Dashboard, Document, EmailTemplate and Report are folder components, so we have to mention they folder name in order to delete. For example, my dashboard is demoDashboard and its in folder demoFolder. Then the component name will be demoFolder/demoDashboard. We can also add multiple component names of same type separated by semicolon (;).

Related links:

View complete code on GitHub

Install Unmanaged package

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Recent Posts

salesforce for financial services transforming customer engagement operational effectiveness
Salesforce for Financial Services: Transforming Customer Engagement & Operational Effectiveness
einstein copilot transform your salesforce experience
How Einstein Copilot Actions Can Transform Your Salesforce Experience
how can salesforce experience cloud drive customer engagement rates
How Can Salesforce Experience Cloud Drive Customer Engagement Rates?
salesforce world tour essentials mumbai
Salesforce World Tour Essentials Mumbai (19 June, 2024)
benefits of using salesforce for higher education Industry
Benefits Of Using Salesforce For Higher Education Industry?
Scroll to Top