Salesforce Obscura: Auto-copy a File to a Rich Text Field with Flows
Option 2 of 2 — using ScreenFlows to upload the file from an Experience Cloud setting
In our last article, we demonstrated how to use FormAssembly to capture and load a file into Salesforce, then use a Flow to automatically display that uploaded file in a Rich Text field.
This article will take you step-by-step through accomplishing the same goal with Screen Flows in a 100% Salesforce native way. Note that it may look like a lot of steps, but I’ve broken it down into a true step-by-step tutorial so that those less familiar with Flows and Screen Flows can follow along.
Example Use Case and Solution Overview
For this article, we tackle a very common request from our clients: getting a Customer Community User to upload their own photo and displaying that photo in a rich text field for internal users to view and use.
Solution Overview:
- Prepare the Contact record with a Rich Text field to display the photo and a field to help control the Flow
- Create a Screen Flow and set up its Resources
(2a) New Resource: ContactRecordID
(2b) New Resource: CVID
(2c) New Resource: UploadedCVIds
(2d) New Resource: TypeOfFile
(2e) New Resource: DocumentURL
Getting the URL for the Image tag - Create the initial logic and file upload screen
(3a) New Get Records: Find Related Contact Record
(3b) Decision: Do we have a contact record at all? With or without a photo?
(3c) Add Element: Screen under “Yes — With Photo”
(3d) Add Element: Screen under “No”
(3f) Add Element: Screen under “Yes — No Photo” - Process the uploaded file and update the Contact record
(4a) Loop: Process Content Version IDs
(4b) Assignment: Assign CVID variable
(4d) Assignment: Set Photo Fields on Contact Record
(4e) Update Record: Update Contact
(4f) Save and Activate your Flow - Embed the Screen Flow into the Experience Cloud page
(5a) Add Flow to Contact Detail Page in the Builder
(5b) Configure the Flow Component - Test
Important Notes for this solution: Once you begin working in Experience Cloud, a variety of permissions and visibility issues come into the picture. This article does not address this solution for public-facing pages where guest user permissions become important, though maybe one day we will. We are demonstrating something that is relatively straightforward on the permissions side and assuming the logged-in user has a Customer Communities license and, therefore, access to view their own Contact record. We are assuming that, if you’re reading this, you already have a correctly-set-up Experience Cloud with all relevant permissions already set.
(Step 1) Create 2 new fields on your object.
Add the following fields to your object:
- A text field, 18 characters long, to contain the ID of the file you will upload. I called mine “Photo CV ID” (note, you could make this a checkbox or anything else, it just aids in routing your screen flow)
- A rich text field to display the photo itself. I called mine “Photo” and just took the defaults for a rich text field.
(Step 2) Create a new Screen Flow and Set Up Resources
The overview of my Screen Flow is below. I am first confirming that I get the information I need (Contact ID from the Experience Cloud page, in this case) and then determining which of 3 screens to show — a “thank you” message, our main uploader, or an error screen.
This part of the documentation focuses on the Screen Flow setup. Step 3 shows setting up the file capture itself, and Step 4 shows the processing logic.
You will need several Resources to make this work, outlined in detail below. Create all new Resources in the Toolbox, under “Manager,” by clicking “New Resource.”
(2a) New Resource: ContactRecordID
Explanation: to construct this solution, you have to know which Contact record you want to attach the new file to and display the uploaded photo within. In the Experience Cloud builder, as long as you’re using a Record Page, you can pass the ID of the Contact Record into a Flow variable. So you are setting up this variable to anticipate embedding it into Experience Cloud.
- Resource Type: Variable
- API Name: ContactRecordId
- Description: This variable contains the ID of the Contact Record that embeds this flow
- Data Type: Text
- Make sure to check the box next to Available for Input
(2b) New Resource: CVID
Explanation: the key to making a photo display in a rich text field is having the Content Version ID of the uploaded file. So you create a variable to contain it after the upload process. This isn’t strictly necessary but it makes our lives much easier.
- Resource Type: Variable
- API Name: CVID
- Description: Content Version ID for use in a formula
- Data Type: Text
(2c) New Resource: UploadedCVIds
Explanation: the file uploader available in Screen Flows will return the IDs of the uploaded files (content documents) and content versions, but always as a list (collection). So you are creating a collection variable to contain the results, even though you are only going to allow a single file to be uploaded in the flow.
- Resource Type: Variable
- API Name: UploadedCVIds
- Description: This collection contains the record set required by the file uploader to get the Content Version IDs back
- Data Type: Text
- Check the box next to “Allow multiple values (collection)”
(2d) New Resource: TypeOfFile
Explanation: the Rich Text display needs to know what type of file it is displaying so that it can correctly generate a preview. This variable contains that information to help in our final step, the display formula.
- Resource Type: Variable
- API Name: TypeOfFile
- Description: The uploaded photo can be one of several image types. This helps the rich text field display the photo correctly.
- Data Type: Text
(2e) New Resource: DocumentURL
Explanation: This is where you bring it all together into the formula that actually displays the uploaded file in your new Rich Text field. It is an HTML image tag that references the content version ID and the type of file (resources you created above). Note that they are referenced in the formula, so if you changed the names of the resources you will need to change them in the formula too.
- Resource Type: Formula
- API Name: DocumentURL
- Description: This is the formula that allows for the preview in the rich text field.
- Data Type: Text
- Formula:
‘<p><img src=”https://REPLACEME.file.force.com/sfc/servlet.shepherd/version/renditionDownload?rendition=ORIGINAL_'+{!TypeOfFile}+'&versionId='+{!CVID}+'&operationContext=CHATTER" alt=”Photo” height=”200" width=”200"></img></p>’
IMPORTANT: Replace REPLACEME in the formula above with your actual Salesforce domain. From any record page, just grab the part of the URL before “.lightning.force.com”
The part in red is what I would put in place of REPLACEME below. Your URL is unique to your Salesforce instance. Note that if you are building this in a sandbox, you will need to update your Flow with your production URL when you move this into production.
IMPORTANT: Getting the URL for the Image tag
The URLs for Salesforce resources change periodically. You may find that this formula works great for awhile and then suddenly stops working, in which case I’d immediately look to the URLs.
To get the URL currently in use for the photo previews, I:
- Manually upload an image file to a Contact record
- Click on the file to open it in the File Preview. Right click (command+click on mac) and copy the preview to my clipboard.
- Paste from my clipboard into a Rich Text field on Contact and save my changes.
- Use dataloader.io to export only the record I’m testing with, and only my rich text field. The html for the working, pasted image will show up in plain text in the export, allowing you to mimic it in this formula field.
(Step 3) Add the Initial Logic and Screens to your Screen Flow
Now that you have all of the prep work done, you can start building your screen flow. It’s a best practice to confirm that you have all of the data you’re expecting before jumping right into processing it, so we’re going to take advantage of that to show one of 3 different scenarios: (1) we have already gotten the photo; (2) we don’t yet have a photo; or (3) something is wrong and we don’t have all the data we need.
(3a) New Get Records: Find Related Contact Record
Explanation: This Screen Flow is intended to be embedded in a Contact Record page. We need to confirm we have the Contact ID and can access that record before going any further. To do this, we use the Resource “ContactRecordID” that we set up in step (2a) to find the full record details and make them available later in the Flow.
- Label: Find Related Contact Record
- Description: Look up the Contact record for the ID that was passed in to the ContactRecordID variable
- Get Records of This Object: Contact
- Filter Contact Records: All Conditions are Met (AND)
- Filter Contact Records: Field: Id [equals] ContactRecordID
- Sort Order: Not Sorted
- How Many Records to Store: Only the first record
- How to Store Record Data: Automatically store all fields
(3b) Decision: Do we have a contact record at all? With or without a photo?
Explanation: For this example, we want the user to see a different message once they have successfully uploaded their photo. Because it’s not relevant for this article, I’ve put in some very simple placeholder screens. You might choose to display the photo and a “change file” link, or any other logic that your actual use case demands.
We use the new text field Photo CV ID that we set up in step 1 to help us determine easily whether we have what we’re looking for or not.
Note that you can use a simple checkbox, look for the presence of data in the Photo field, or any other type of logic you choose. I tend to capture more information to aid in debugging wherever possible, so having the full Content Version ID can give me more information than a checkbox when I’m trying to troubleshoot permissions or duplicates (for example).
New Decision:
- Label: Did we find a record?
- Description: This screen flow can only be launched from a Contact page so we should always find a record. It is still a best practice to check for errors.
Outcomes 1:
- Label: Yes — With Photo
- Condition Requirements to Execute Outcome: All Conditions are Met (AND)
- Set up the first condition: Contact from Find_Related_Contact_Record [is null] False
In this condition, we are asking if the record itself is null / did we get anything back from the lookup in step 3a. Under Resource, choose the Find Related Contact Record result that was automatically created when you set up step 3a. Click on it. It will populate your field with {!Find_Related_Contact_Record.} and prompt you to select other fields.
Just erase the trailing period so that it says {!Find_Related_Contact_Record} to reference the entire variable.
- Set up the second condition: Contact from Find_Related_Contact_Record.Photo_CV_ID__c [is null] False
In this condition we are asking the simpler question of whether the Photo CV ID on the Contact Record is empty or not. Follow the same steps as the first condition but this time look for the Photo CV ID field when prompted for a field selection.
Click + next to Outcome Order to add Outcomes 2:
- Label: Yes — No Photo
- Condition Requirements to Execute Outcome: All Conditions are Met (AND)
- Set up the first condition: Contact from Find_Related_Contact_Record [is null] False
This condition is set up exactly as you did for Outcomes 1. - Set up the second condition: Contact from Find_Related_Contact_Record.Photo_CV_ID__c [is null] True
This condition is set up exactly as you did for Outcomes 2 except you want True instead of False. For this decision, we care about a Contact that exists but does not yet have a photo.
Click on “Default Outcome” and change the label to “No” so that if neither of our outcomes is true, we get an error screen.
(3c) Add Element: Screen under “Yes — With Photo”
Explanation: Your first decision point — yes, we have a contact record and yes, it already has a photo — will just show a simple “thank you” message for this example.
- Click + under “Yes — With Photo” and choose “Screen”
- Screen Properties: Label: Confirm Photo
- Screen Properties: Description: This contact already has a photo or has successfully uploaded one.
- Optional: Configure Header: I also chose to configure the header and uncheck “Show Header” to make the embedded Screen Flow a bit more seamless
- Optional: Configure Footer: I chose to uncheck Show Footer entirely. This is informational only, no actions are needed so no buttons should be visible.
Under “Components” on the left-hand side, find “Display Text” and drag it into the center panel.
- API Name: ConfirmPhoto
- In the rich text editor, to display your message: We have received your photo. Thank you.
(3d) Add Element: Screen under “No”
Explanation: Keeping it simple, we want to add another Display Text if we have an error. So we’re going to jump over the middle decision point for right now and knock out the other quick screen before moving ahead.
- Click + under “No” and choose “Screen”
- Screen Properties: Label: Error Contact Not Found
- Screen Properties: Description: This screen only appears if there is a problem passing the Contact ID or a problem with the end user reading the Contact Record.
- Optional: Configure Header: Uncheck “Show Header” as above
- Optional: Configure Footer: Uncheck Show Footer as above
Under “Components” on the left-hand side, find “Display Text” and drag it into the center panel.
- API Name: ContactError
- In the rich text editor, to display your message: We can’t find the Contact ID, sorry.
(3f) Add Element: Screen under “Yes — No Photo”
Explanation: Finally we get to the good stuff. This is where we actually set up our File Uploader. Please note that the file uploader is tricky — the variables it shows by default are not the ones you want! You want the ones in Advanced, as in the steps below.
When you have set this up exactly as below, you will have a prompt for a Community user to upload a single file as a child of their Contact record. That file will be limited to just image types (gif, jpg and png) and most important it will give us back the Content Version ID of the newly-uploaded file so that we can process it in Step 4.
- Click + under “Yes — No Photo” and choose “Screen”
- Screen Properties: Label: Upload Your Photo
- Screen Properties: Description: Upload a photo of yourself to share with our team.
- Configure Header: Check “Show Header” and (optional) Show Help Text.
- Configure Footer: Check “Show Footer”.
- Configure Footer: Next or Finish Button: Use a Custom Label (“Save File”)
- Configure Footer: Previous Button: Hide Previous
- Configure Footer: Pause Button: Hide Pause
Under “Components” on the left-hand side, find “File Upload” and drag it into the center panel.
- API Name: UploadPhoto
- File Upload Label: Upload
- Accepted Formats: .jpg,.png,.gif
Note: you can just type in a comma-separated list of accepted formats - Allow Multiple Files: False
Note: click into this field and type false. It will set the field to {!$GlobalConstant.False} - Content Document IDs: leave this blank
- Content Version IDs: LEAVE THIS BLANK
This version of the settings will not do what we want. We’ll handle this in Advanced, below. - Disabled: leave this blank
- Hover Text: leave this blank or put some sort of help text in if you prefer
- Related Record ID: {!ContactRecordID}
Find the ContactRecordID resource you set up in step 2a. - Uploaded File Names: leave this blank
- Optional: Set Component Visibility: I have left this to “Always”
IMPORTANT: Expand the Advanced dialogue box
- Check the box for “Manually Assign Variables”
- Leave everything else blank except for Content Version IDs
- Content Version IDs: {!UploadedCVIds}
Find the UploadedCVIds collection resource you created in step 2c. - Revisited Screen Values: Check next to “Refresh inputs to incorporate changes elsewhere in the flow”
(Step 4) Process the uploaded file and update the Contact record
Still in the “Yes--No Photo” logic branch, this last step of the Flow will process the uploaded file so that it is put into the Contact Rich Text field. It’s a little unintuitive if you’re not a programmer, but basically even though we are limiting the File Uploader to only one file, it will return our Content Version ID in a collection. So we have to process that collection as if there were more records in it, even though there aren’t.
This step proceeds down the middle of the Flow, directly under our Upload your Photo screen.
(4a) Loop: Process Content Version IDs
Explanation: as stated in the Step 4 introduction, even though we know we are only getting back one ID, Salesforce doesn’t know that. So we have to process the results of our file upload as if we had a whole collection. That requires a loop.
- Click + under the Upload your Photo screen icon
- Choose Loop
- Label: Process Content Version IDs
- Description: Although we only allow one file to be uploaded, the value is returned in a list so we have to process it in a loop
- Collection Variable: {!UploadedCVIds}
Use the UploadedCVIds variable you created in step 2c and populated from the Flow Uploader Advanced Setup in step 3f - Direction: First item to last item
(4b) Assignment: Assign CVID variable
Explanation: you will use the CVID resource you created in step 2b for the next step, in the formula that displays the photo, and to populate the Contact field that controls which screen shows up for the end user.
Assign the value that is in whatever loop of the collection we’re on — remember have only one, that’s the only way this works — to the CVID resource.
- Click + under the loop you added in step 4a
- Choose Assignment
- Label: Assign CVID variable
- Description: Although we only allow one file to be uploaded, the value is returned in a list so we have to process it in a loop
- Set Variable Values: Variable: CVID
Use the CVID resource you created in step 2b - Set Variable Values: Operator: Equals
- Set Variable Values: Value: Current Item from Loop Process_Content_Version_IDs
(4c) New Get Records: Get CV from ID
Explanation: While we have, in hand, the ID of the Content Version, we do not have any other data about it. So we have to go look it up to populate the File Type.
Note that this is being done in a loop so it’s extremely poor practice to do this unless you are 100% sure that you are allowing ONLY ONE file. Otherwise you risk performing queries in a loop, which is never OK in Salesforce (or really anywhere else).
- Click + under the Assign CVID variable you added in step 4b
- Label: Get CV from ID
- Description: Use the ID of the Content Version to obtain the file type
- Get Records of This Object: Content Version
- Filter Contact Records: All Conditions are Met (AND)
- Filter Contact Records: Field: Id [equals] CVID
Use the CVID resource you created in step 2b - Sort Order: Not Sorted
- How Many Records to Store: Only the first record
- How to Store Record Data: Choose fields and assign variables (advanced)
- Where to Store Field Values: In separate variables
Store the Content Version’s File Type field in the resource TypeOfFile you set up in step 2d - Field: File Type
- Variable: TypeOfFile
(4d) Assignment: Set Photo Fields on Contact Record
Explanation: This takes place AFTER the loop is over (on the “After Last” line). We set our variables in the loop, and now we need to assign them to the Contact Record that we are working with, which we selected at the very start of our Flow in step 3a.
- Click + on the “After Last” line
- Choose Assignment
- Label: Set Photo Fields on Contact Record
- Description: Set the Photo-related fields on the Contact we found to start this flow
Variable 1: Contact from Find_Related_Contact_Record.Photo__c [equals] DocumentURL
- Set Variable Values: Variable: Contact from Find_Related_Contact_Record.Photo__c
Use the Find Related Contact Record that was automatically created for you in step 3a, and select the rich text Photo field you created in step 1. We are populating this field on the Contact record. - Set Variable Values: Operator: Equals
- Set Variable Values: Value: DocumentURL
Use the DocumentURL formula resource you created in step 2e to populate the rich text field Photo on Contact.
Variable 2: Contact from Find_Related_Contact_Record.Photo_CV_ID__c [equals] CVID
- Set Variable Values: Variable: Contact from Find_Related_Contact_Record.Photo_CV_ID__c
Use the Find Related Contact Record that was automatically created for you in step 3a, and select the text Photo CV ID field you created in step 1. - Set Variable Values: Operator: Equals
- Set Variable Values: Value: CVID
Use the CVID resource you created in step 2b to populate the rich text field Photo on Contact.
(4e) Update Record: Update Contact
Explanation: The last step in the flow! You made it through the hard part!! All this does is tell Salesforce to actually write the updated fields (from step 4d) onto the Contact record we found at the very start of this process in step 3a.
- Click + under the Assignment you created in the last step
- Choose “Update Records”
- Label: Update Contact
- Description: Update the Contact that started this Flow with the data collected from the file upload
- How to Find Records to Update and Set Their Values: Use the IDs and all field values from a record or record collection
- Record or Record Collection: Contact from Find_Related_Contact_Record
Use the Find Related Contact Record that was automatically created for you in step 3a.
(4f) Save and Activate your Flow
Now is a really good time to pause and celebrate making it this far.
(Step 5) Embed Your Flow into Experience Cloud
This last step is really just icing. And don’t worry, it’s very simple! If you already know how to embed a Screen Flow on a Record Page in Experience Cloud, you can skip ahead to the testing.
Since we’re assuming that you already know how to set up Experience Cloud and how to set up permissions and visibility, we’re going to skip the how-to on creating a new Record page for the Contact Object. This example is working with a Contact Detail page that was already set up.
(5a) Open your Contact Detail Page in the Builder and add a Flow component
Our page has a custom tab setup and I created a new tab just for this Flow called “Photo”. I have embedded a Flow Component on this tab, as in the screenshot below.
(5b) Configure the Flow Component
Explanation: This step links the Flow you created above to the record page. It uses the Input variable you set up in step 2a. Remember when you checked the “Available for Input” box on the ContactRecordID resource? that’s what allows the Flow Component to pass the Record ID in to your Screen Flow.
- Click on the component to open up its contextual configuration window.
- Flow: Upload Photo From Community
or whatever you named the flow you created in steps 2–4 - Check the box next to “Pass record ID into this variable” under the “ContactRecordID” variable.
- Save and publish.
(Step 6) Test it out
Please note that it is beyond what I can tackle in this article to address all of the permissions issues and things that could stand in your way in a Customer Communities license setting, so make sure the user you’re testing with can actually edit their own Contact record and upload files before testing your solution.
Log in to your community as a user. Upload a file, and you should get:
- A File attached to the Contact record for the user you are logging in as
- A value in the Photo CV ID field
- A 200x200 view of your file in your rich text Photo field
Pictured above: The attached file, the Photo CV ID and the uploaded photo in your rich text field
Congratulations! You’ve learned a lot about Screen Flows, Flows, Content Documents and Content Versions, and how it all works with Experience Cloud! Have fun with your new expertise!
My firm (Deep Why Design) specializes in weird use cases and things nobody else wants to touch — from big and innovative shared platforms and custom solutions to teaching accidental admins how to get the most out of their systems. My areas of expertise are fire jumping and forensics — call when there’s an unsolvable problem — and the design and management of large, integrated, and complex data systems in highly constrained situations.