How I Solved It: File Deletion Flow
- Michael Kolodner
- 2 hours ago
- 10 min read
I know I'm not the only one that thinks it's a pain to work with Files in Salesforce. Stories of frustration, confusion, and downright bafflement abound. The object structure for what seems like a relatively simple and convenient File uploader on the page layout

is anything but simple under the hood.
ContentDocument, ContentDocumentLink, ContentVersion, ContentHistory, ownership, permissions, ... The list of considerations just keeps going. I could probably try to write a post explaining it in simpler terms but, frankly, I don't think I really understand it, so I would probably just lead you astray in some way.

But I recently built something that turns out to be surprisingly simple and solves a common problem with Files, so I'm going to share that with you instead.
The Business Problem
Lots of organizations use Files in Salesforce, usually in the ways you would pretty much expect. You're working with a record, there is some kind of file related to that record, so you upload it for easy access and reference. This could be a photo of a contact, or a signed contract, or a mail-merged letter generated from the record that you are going to send out to a constituent, or a million other things.
Let me introduce you to For Pete's Sake (FPS), a great organization that works with cancer patients and their families to give them "a break from cancer," a respite experience in which they can be together as a family to bond away from the day-to-day of regular life. FPS does their program management on Salesforce. After a patient is nominated, FPS staff goes through a series of steps to "onboard" the person and their family and get them ready for the respite experience. Most of that work is done on a custom object called Onboarding. In the course of working with the onboarding record, we have various files, including online forms that the participant fills, a family photo the patient sends, and the like. A perfect use case for Files!
I've been working with FPS for two years, but I think they were using Files before I came along. I'm not even certain their original Salesforce implementer taught them to do so, so much as that they may have seen the Files related list and adopted it organically. (For a while they were also separately uploading the files to Box.com, a bit of duplicative work that I'm not sure they could articulate the value of.) Several months ago I worked with FPS to regularlize some of their usage of Files and to implement some automatic document generation to replace documents they were manually creating from a Word template and then uploading to the onboarding record. So far, so good. Basic Files stuff.
But with the automatic document generation, naturally, we started to find some cases where the "final" version wasn't so final. Perhaps the dates of a respite trip changed. Or perhaps some mistake in the onboarding record is noticed just before the "Final Letter" is about to go into the box being mailed to the patient. These things happen. And FPS was savvy enough to realize that generally the solution here is to just re-create the document and upload the new version, whether that's a manual process or the re-trigging of some automation. But in those cases, the incorrect file is still attached to the record. That's likely to create some confusion!
When asked what was the "right" way to deal with this, I just told them, "Go ahead and click Delete on the original file." Silly me, assuming they had the same options I see. They have no delete option on the file! I delved into permissions, but that wasn't the issue. If the original file was uploaded by one person and someone else is now working on the record, they can't delete the old file—it's not a question of permissions. Files can only be deleted by the person that originally uploaded them (the file's "owner") or by someone with Modify All Data, like a system administrator.
I was seeing a delete button because I have Modify All Data permisssions. I even confirmed that if I gave Modify All Data to one of the program coordinators, she then could delete. But I really don't want to give Modify All Data to people. (There's a reason we abbreviate it as MAD!) Besides the implications for data security, I really hate the user experience that we get when we have MAD, where even fields that are Read Only on the page layout show as editable.
Let me note a few other things about this problem:
It's not that Files (ContentDocuments) have some kind of sharing model. It's simply binary: You own the file or you don't. You have Modify All Data or you don't.
Before I managed to solve the actual issue, this meant I had a workaround: Get people to tell me when a file needs deletion and I can do it as the sysadmin. Not exactly efficient or automated, but at least I can keep things clean.
While it's ContentDocument that stores the file, it's ContentDocumentLink that relates the file to a record. If you delete the latter, the file is still taking up storage on Salesforce servers, if you care. (Maybe I shouldn't, but I do.)
I've also learned, through much trial and [mostly] error, that the ContentWhatever objects have other annoying limitations.
The worst: You can't really access ContentDocumentLink in SOQL. You have to query for only a single ContentDocumentId. (But if I already knew the Id, I wouldn't need to be querying!)
You can't really work with ContentWhatever in the UI. Generally you can't add fields, there is no page layout, you can' modify the Files related list...
You can't make record triggered flows on ContentWhatever. (Even if you could, it's not like you could change the sharing in some useful way.)
Community to the Rescue?
As soon as I saw that Help article linked above I knew I was going to be in trouble. But there are some smart people in the Trailblazer Community! Maybe someone had solved this before? Further searching was only turning up more commentary on the problem, not much in the way of solutions. Eventually I posted some questions online and get traction on this Ohana Slack thread. Erik Lopez suggested that perhaps a screen flow could assign Modify All Data, allow a user to delete a file, and then revoke Modify All Data. Interesting...
But the first thing I realized is that you can't actually put a screen flow onto a File. (There's no page layout nor Lightning Record Page for files.) My screen flow is going to have to go on the onboarding record. And that means the screen flow is going to have to present all the available files for the user to choose which to delete. Now we've run into a different problem.
How I Solved It, v1
Thought work complete, I set about seeing what kind of screen flow I could build.
Step One: spin up a sandbox just for this purpose. Besides being a general "Best Practice," this is going to have some additional benefits:
I don't have to worry that I upload a bunch of files and they're going to take up storage forever.
I can make a bunch of test Onboardings and put files on them without confusing users.
I won't have any chance of deleting important files in production.
Remember, above, that I said working with these objects in SOQL is a pain in the neck? If it's hard in SOQL, then it's not going to be easier using Get Records elements in flows. It took me a little bit of fooling around, but I eventually realized that the screen flow was going to pass in the record Id of the onboarding and I have to Get all the ContentDocumentLinks that attach files to that record.

Then I'm going to show those ContentDocumentLinks in a datatable for the user to select which to delete. [And here's a surprising early win: I'm going to be able to use the standard component from Salesforce. As much as I love and appreciate the Datatable from UnofficialSF, I'm happy to be able to make this flow with standard components.]
Sadly, even though my Get Records is set to "Automatically store all fields," I found out that none of those fields is the filename. Clearly the users need the name to select which is the right file to delete—the Id of the document is not going to be useful. It's not an option to add a formula field on ContentDocumentLink that spans to the file. I'll spare you the details, but suffice it to say that I tried everything I could think of to get that title to display from the ContentDocumentLink, to no avail. Eventually I admitted defeat and tried looping over the ContentDocumentLinks to get the related ContentDocuments.

In the Wins column: This works. My datatable on a screen right after the loop (not shown) can show columns for the filename, owner, and creation date. Yay!
But I'm executing SOQL within a loop, a rather big No No. That is not efficient and it runs a good chance of running up against governor limits.
Side note: Realistically, I could have just taken the win and moved on. This screen flow is going to run only on records that have fewer than ten—often fewer than three—files attached. It will work. The extra computing power used is negligible. And the screen after the loop starts a new transaction with a new set of governor limits. But that pink element within a loop did not make me happy.
How I Solved It, v2
Tip o' the hat, again to Erik Lopez who suggested that we could probably avoid the SOQL in a loop if within the loop we assigned the Title from the ContentDocumentLink to a text variable. In that context I am able to span through the relationship (ContentDocumentLink > ContentDocument > Title) and get to the title of the file. In fact, I replaced the pink element from the version above with an Assignment that sets three fields on a record variable:
the ContentDocument Id (the Id of the actual file),
the Title,
and the Created Date. (If two files have similar names, this might help the user delete the right one.)

All the fields I need to show in my datatable on the next screen are now waiting for me.
Here's the entire flow:

In words it:
Gets ContentDocumentLinks related to the record.
Loops over them assigning variables.
Adds those variables to a collection.
Has a screen with a datable to select one (only one).
Follows up with an "Are You Sure?" screen.
Then deletes the selected file.
Reduce, Re-use, Recycle ♻️
I realized after building it that this flow is actually 100% object agnostic. Though at the moment I'm only planning to put it on an onboarding page, the flow itself does not care what page(s) it might be put on. It accepts (actually requires) a record Id as input. But that's just a text string. Using that Id, the flow loops over ContentDocumentLinks and finds information about the related ContentDocuments. The same screen flow can run from any object, passing in its Id, and the Get Records (screenshot above) will work without any modification. I can use this in other places within For Pete's Sake's Salesforce or even deploy it to other client orgs and use it there exactly as built. That's pretty cool and pretty rare, in my experience.
Try This At Home!
This is a pretty handy little flow and something you should be able to build for yourself using just this blog post. So go try it and let me know what you find!
Your Org Might Be Different
But before I send you off to your Flow Canvas, I want to make explicit a few of the considerations and tradeoffs in the way I have built this. You might want or need to make different choices.
System Context, No Sharing
I noted, above, that Erik Lopez suggested I could assign a permission set with Modify All Data, allow the user to delete the file, and then revoke the permission set. But you see that I didn't do that. Instead, I have the flow running in "System Context Without Sharing—Access All Data" a setting available in the Advanced Settings when you save a flow.

This made for a simpler flow that was easier to build—I didn't need to find and assign a permission set, then revoke it at the end. Your comfort with this setup may vary.
Deletes the File (ContentDocument) Directly
As noted in the warning on the selection screen, my flow deletes the File (the ContentDocument), which cascade-deletes any ContentDocumentLinks sharing it to records. I could have built the flow to un-share to the record (delete the ContentDocumentLink to the record). But in For Pete's Sake's use case we are confident that the files are only attached to one record. So if we were to un-share, then the file would just be orphaned and taking up server space for no reason. I'm confident that deleting the file is the right action to take.
In theory there could be a share to another record. Certainly in your org there could be shares to other records. In that case, you might need or want to delete just the ContentDocumentLink. But then you have the possibility of orphaned files that you might want to deal with. I can think of a few suggestions:
Add a notification to the sysadmin that there is a file no longer shared to this record. (Note: The admin is going to have a challenge to find files with no ContentDocumentLinks. It's possible in SOQL, I think, but...)
Delete the ContentDocumentLinks, leave the file orphaned, create some kind of cleanup via a scheduled flow that looks files with no ContentDocumentLinks. Again, I think this is possible, but it could have unexpected challenges.
Have the screen flow check if there are other links to the file, only deleing the file if there are none. But if there are additional ContentDocumentLinks and your user is deleting this file because it needs to be updated, then you have other issues.
Versioning
This is a perfect segue to come back to the idea of versioning. As noted above, the ContentWhatever pile of objects includes ContentVersion, which exists so that it's possible to save multiple versions of files in Salesforce. In the For Pete's Sake use case, for example, it might be that the "right" solution would be to actually update the file version, rather than deleting and re-uploading. But figuring out how to do that would be even more complicated. I decided not to even consider that path. I'd love to hear if you've ever used versions.
Does Anyone Use This?
As noted, I'm not sure how many orgs use versions. In addition, my gut says there are very few orgs that actually take advantage of the ability to share files to multiple records in the first place. It's an interesting idea and technical setup and I can see why Salesforce wanted to build Files that way, but it seems cumbersome to use, at best. Let me know if you think I'm wrong!