How To

Limit Record Deletion to Records User Created using Clicks, Not Code

NoCodeNovemberLinkedIn

Have you had a use case where you only want the user to delete records s/he created and not all records in the object? Unlike with the edit record function, you can’t use validation rules to prevent record deletion. All roads seemed to lead to solving this via a trigger. Being an Awesome Admin, I wasn’t satisfied with that answer and was determined to find a way to solve for this declaratively.

In support of #NoCodeNovember, for the entire month of November, I joined a success community group (@No Code November) dedicated to finding creative ways to deliver features to customers without one line of code. [Read the post on Button Click Admin 5 Reasons to Go #NoCodeNovember to learn more about the group’s genesis.]

I was so excited when I solved the following use case using clicks instead of code. This works in both Salesforce Classic and Lightning Experience.

Ideally, you should avoid allowing business users to delete records in the system and limit this function to a select few as this may leave potential gaps in your data. However, if there is a use case where you must allow users the ability to delete records, then here is a declarative solution. I generalized the below use case to a standard Salesforce object but you can tweak this solution for any object.

Here a few lessons learned from implementing this use case:

  • Use custom permissions to give users access to custom processes. A custom permission can be assigned to users through a profile or permission set.
  • Provide descriptions where provided in Salesforce. This may be tedious step, I know, but your future self will thank you when you are trying to remember what you configured or assist other/future admins when troubleshooting or enhancing what was built. This includes noting where a visualforce page is referenced, a visual flow is invoked from, the purpose of a flow variable, etc.
  • When doing a comparison of IDs (such as, created by vs current user, etc.), verify that you are comparing an apple to apple and not an apple to an orange. Comparing a 15 character SFDC ID to a 18 character SFDC ID will ALWAYS fail. Use the formula CASESAFEID () which takes the 15 character SFDC ID and converts it to 18 characters.
  • To invoke a visual flow to override standard Salesforce delete function, it’s not just about swapping out the delete button on the page layout. You should override the standard delete button with a visualforce page that invokes the process created by visual flow.
  • Include a send email function to send flow fault notification information to your production support staff. This flow element should be placed before the fault screen or else it will never execute.    

Business Use Case: Addison Dogster is a system administrator at Universal Container. Sammy Sunshine is an Operations Manager who requested that business users be able to delete contact records they created in Salesforce.

Addison thought this was going to be an easy task, “I just grant the delete permission to the contact object and I’m done.” However, after the delete permission was enabled on the contact object for the business user profile in her sandbox, Addison noticed in her testing that the business user can delete records s/he created AND records s/he was the record owner of.

Sammy Sunshine clarified that contact records created by another user, such as integration account, system administrator, marketing, etc. that are owned by the business user should not be deleted by the business user.  

Solution: While Addison could have easily thrown this over to a developer to help her write a trigger, Addison wanted to solve this using out of the box declarative actions.

She was able to meet Sammy’s requirements through configuration using flow that allows the record deletion if the user is the record creator or the user has the custom permission to delete the records. She replaced the standard Delete button with a custom button that invokes the flow. There is one problem. The custom delete button is not invoked from the “delete” link on the contact list view. Addison scrapped the custom delete button and instead overrode the standard delete button with a visualforce page that invokes the flow

Her final solution involves a custom permission, visual workflow, overriding the standard delete button with a visualforce page to invoke the flow.

Note: Navigation notes below are for Salesforce Classic. You can search the setup for the configuration items.

Quick Steps:

1. Create a custom permission (Develop | Custom Permissions).

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this permission is used for, where it is referenced.

CustomPermission

2. Navigate to the profile(s) (Manage Users | Profiles | select profile that need the ability to delete all contact records and assign the custom permission) and add the custom permission created in Step 1.

EnableCustomPermissions

3. Create a visual workflow (Create | Workflows & Approvals | Flows). This will allow record deletion if the user is the record creator OR has the Delete Contact Record custom permission.

DeleteContactRecordFlowVisual

A. We’re going to create variables that will be used in this visual flow for the contact record ID and the contact creator ID.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements.

varContactID

varContactCreatorID

B. Now, we need to create two formulas.

CurrentUserSFDCID

The first one will convert the current user’s ID (15 character ID) to the 18 character SFDC ID. The ID will be compared to the CreatedBy field (18 characters).

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements.

DeleteTeamMemberFormula

The other formula is to reference the custom permission Delete Contact Record. Please ensure that the value data type is Boolean, not text.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what formula is used for when troubleshooting or making enhancements.

C. Create a Record Lookup flow element on the Contact object based on the ContactID variable. Store the record’s CreatedByID in the varContactCreatorID variable.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements. (I didn’t provide one below as the description took up too much real estate for the screenshot.)

RecordLookupContact

D. Set the Record Lookup flow element as the Start Element.

RecordLookupStartingElement

E. Create a Decision flow element to determine whether the current user matches the record’s creator.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements.

IsContactOwnerDecisionF. Create a Record Delete flow element that deletes the record where the ID matches the varContactID variable and CreatedByID matches the CurrentUserSFDCID formula value.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements. (I didn’t provide one below as the description took up too much real estate for the screenshot.)

DeleteRecord-IsContactCreatorG. Create a Decision flow element to determine whether the current user has the custom permission Delete Contact Records.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements.

CustomPermissionDecision

H. Create a Record Delete flow element that deletes the record where the ID matches the varContactID variable.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements.

I. Create a Screen flow element that shows an error message to the user that s/he can’t delete the record because s/he is not the record creator.

Can'tDeleteContactRecord

Best Practice: Create a Send Email static flow element to send the fault email to the system administrator to alert them that there was a fault on your visual flow. All record or fast data actions (lookup, create, delete, update) should connect to this flow element with the fault connector. Note: The send email flow needs to happen before the fault data screen. If the send email is placed after the error screen, the send email will never execute.

Note: To avoid “hardcoding” email addresses in a fault email, refer to a post Using Custom Metadata Type in Visual Workflow Fault Emails.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for when troubleshooting or making enhancements.

SendFaultEmail

J. Create a Screen flow element that shows the fault error message to the user that there was an issue with the data action.

ErrorScreen

K. Make all the connectors, decision connector and fault connectors match the visual flow completed diagram below.

DeleteContactRecordFlowVisualL. Click on the Save button and provide the properties below.

FlowPropertiesM. Close the flow.

N. Navigate to the flow screen (Create | Workflows & Approvals | Flows). Click on the Activate link next to the DeleteContactRecord flow.

ActivateFlow

4. Create a visualforce page (Develop | Visualforce pages) that invokes the visual workflow.

DeleteContactRecordVisualForce

Don’t let this part scare you. I’m not a developer so if I can copy, paste and tweak, so can you. I quote Mike Gerholdt at the Dreamforce 15 Admin Keynote “There is no shame in copy and paste.”

Here are the contents of your visualforce page:

<apex:page standardController=”Contact“>
    <flow:interview name=DeleteContactRecord” finishLocation=”{!URLFOR(‘/003/o‘)}”>
    <apex:param name=”varContactID” value=”{!Contact.Id}”/></flow:interview>
</apex:page>

Let me explain the bolded items you will need to modify in your visualforce page:

  • standardController: Reference the object (API name) that this visual flow will execute on.
  • interview name: This is the name of your Flow Name.
  • finishLocation: This the URL for object home page. In my example, I’m taking the user back to the Contacts home page.
  • param name: This is the variable name in your flow. In this example, we are passing the contact record ID as variable varContactID used in the visual flow. Note: The variable name must match exactly to the variable name in your visual flow.

5. Add the newly created visualforce page to the profile(s) that need to execute the visual workflow. By default, it is automatically added to the System Administrator profile.

EnableVisualforceAccess

6. Now you need to override the standard Delete function for the Contact object. Go to Customize | Contacts | Buttons, Links and Actions,  click the “Edit” link for the Delete button. For the “Override With” option, select the visualforce page created in the previous step.

Best Practice Tip: Provide comments on what the visualforce page does for your/other admin’s future reference.  

OverrideDeleteButton7. Add the “Run Flows” system permission to the profile(s) that need to execute the visual workflow.

8. Give Delete permissions to the Contact object to the profile(s) that need the ability to delete Contact records.

That’s it. Congrats, you’ve implemented the solution!

Now, before you deploy the changes to Production, you need to test your configuration changes.

  1. Login as a user (integration, system administrator, etc.) with the Delete Contact Record custom permission and create several contact records with different contact owners.
  2. Login as a business user who can delete the contact records s/he created.
  3. Create a couple of contact records.
  4. Confirm that the business user can delete records s/he created.
  5. Confirm that the business user cannot delete records that s/he did not create but may or may not be the contact owner.
  6. Login as a user with the Delete Contact Record custom permission.
  7. Confirm that the user delete all contact records.

Deployment Notes/Tips: Make these changes in the following order in Production:

  • The custom permission, visual workflow and visualforce page can be deployed to Production in a change set.
  • The following profile updates will need to be updated manually in Production (or can be deployed using a tool such as Dreamfactory’s Snapshot):
    • Add the Delete permission to the Contact object
    • Run Flows system permission
    • Add visualforce page
    • Add Delete Contact Records custom permission to the profiles that can delete all contact records
  • Manually update the standard Delete button on the Contact object to point to the new visualforce page.
  • Activate the visual flow as it is deployed to Production as inactive.

2 thoughts on “Limit Record Deletion to Records User Created using Clicks, Not Code

  1. Awesome to see this use-case for Custom Permissions! One question that I have is if the business users have API access? If so, delete through the API might bypass the logic checks in the flow since it is invoked through the custom delete button. But, I believe to solve this you could invoke your flow from a very basic before delete trigger (see http://andyinthecloud.com/2014/10/26/calling-flow-from-apex/) and then it would be invoked in all delete cases and you wouldn’t need to override buttons.

    Like

  2. Hi John. Thanks! It’s my first time using custom permissions. Other than our power users (who have the custom permissions), we don’t allow API access but good to know how to prevent this via apex without overriding buttons.

    Like

Leave a comment