Jennifer Lee and Jenn Taylor in an episode of "How I Solved It"

How I Solved It: Auto-Copy a File to a Rich Text Field with Flows

By

Welcome to another post in the “How I Solved It” series. In this series, we do a deep dive into a specific business problem and share how one #AwesomeAdmin chose to solve it. Once you learn how they solved their specific problem, you’ll be inspired to try their solution yourself! Watch how Jenn Taylor tackled the request to let an Experience Cloud user upload a photo to Files and have that photo show up in a rich text field on their contact record.


Key business problem

Many of our clients are in the human services field, and they like to have a photo of the person they’re working with to show prominently on their contact record. Obviously, they can put a photo into a rich text field, but that means getting the person to send a file via email or upload it somewhere, downloading that file, editing the contact record... Not a big deal for a few records, but a lot of work when your caseload is in the hundreds.

These same clients often use Experience Cloud to provide a self-service area for the people they work with. It’s easy to upload a file in Experience Cloud, but that file can get lost in the full list of files and this option doesn’t really solve the problem. We’ve been asked many times to let an Experience Cloud user upload a photo of themselves and have it appear in a rich text field on the contact record without any further effort from the case worker—and with flows, we can!

How I solved it

First, I set up my Contact record to contain the rich text field that will display the photo, and a field that lets me easily identify exactly which File record has the photo in it.

Then, I created a screen flow to prompt the Experience Cloud user to upload their photo. The logic in the flow lets us capture the ID of the uploaded Content Document and Content Version, and construct the underlying HTML that populates the rich text field. It may sound complicated, but it really isn’t!

Finally, I embedded the screen flow in an Experience Cloud record page so that users can upload their own photo and share it with their caseworker.

Business results

Example of a photo that has been inserted into a rich text field when the file is is uploaded.

Using the screen flow embedded in an Experience Cloud page, a program participant can upload their photo and share it with their caseworker. This reduces the burden on internal staff by minimizing how many steps it takes to accomplish their goals.

On the technology side, implementing this flow helped my team understand more about how Files work under the hood—using Content Document and Content Version records to access and embed the image. It let us work directly with populating formatted rich text fields, which comes in handy for a number of situations. And, it helped us grasp how screen flows work in Experience Cloud, which adds a lot of rich functionality to any Experience Cloud deployment.

When you put these elements together, you get a fun way to move pictures around. But when you take them apart, you gain tremendous power to automate just about anything to do with files. For example, we modified this solution to allow easy capture of the date that required files were uploaded, dramatically reducing the data entry time for staff who deal with hundreds or thousands of applications that each require several files to upload.

Keep reading for all of the details and step-by-step instructions for this solution.

Step 1: Create two new fields on your object

Two new fields—one for the ID and one rich text—on the target 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 the flow to show the full cycle of processing that will be created

The overview of my screen flow is shown above. 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 three 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’ll need several resources to make this work, outlined in detail below. Create all new resources in the toolbox, under Manager, by clicking New Resource.

Step 2a: New Resource: ContactRecordID

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’re setting up this variable to anticipate embedding it into Experience Cloud.

Creating the ContactRecordID variable and checking the box for “Available for input” so it can be embedded properly

  • Resource Type: Variable
  • API Name: ContactRecordID
  • Description: This variable contains the ID of the Contact Record that embeds this flow
  • Data Type: Text
  • Availability Outside the Flow: Check the box for “Available for input'”

Step 2b: New Resource: CVID

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.

Creating a single variable to hold the ID of the Content Version of the file uploaded by the user

  • Resource Type: Variable
  • API Name: CVID
  • Description: Content Version ID for use in a formula
  • Data Type: Text

Step 2c: New Resource: UploadedCVIds

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’ll create a collection variable to contain the results, even though you’re only going to allow a single file to be uploaded in the flow.

Creating a collection variable to contain the Content Version IDs returned from the file uploader

  • 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 for “Allow multiple values (collection)”

Step 2d: New Resource: TypeOfFile

The rich text display needs to know what type of file it’s displaying so that it can correctly generate a preview. This variable contains that information to help in our final step, the display formula.

New variable named TypeOfFile to help control the embedded display

  • 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

Step 2e: New Resource: DocumentURL

This is where you bring it all together into the formula that actually displays the uploaded file in your new rich text field. It’s 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’ll need to change them in the formula, too.

The formula that actually performs the photo embed

  • 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>’

Note: When doing a cut and paste of the formula into Flow, please ensure all quotation marks are straight quotations and not curly quotations. Otherwise, it will not work.

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”.

Change this part of the URL, highlighted in red, when constructing your DocumentLink formula. My domain—the part before .lightning.force.com—is highlighted.

The part in red is what I put in place of REPLACEME below. Your URL is unique to your Salesforce instance. Note: If you’re building this in a sandbox, you’ll 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, do the following.

  1. Manually upload an image file to a contact record.
  2. Click on the file to open it in the File Preview. Right click (command+click on Mac) and copy the preview to your clipboard.
  3. Paste from the clipboard into a rich text field on Contact, and save your changes.
  4. Use a data export tool like Dataloader.io or the desktop Data Loader to export only the record you’re testing with, and only your 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

Highlighting the part of the flow illustrated in Step 3

In Step 3, we’ll build the pieces shown in the image above: get the contact, decide what screen we need to show, and show the screens.

Now that you have all of the prep work done, you can start to build your screen flow. It’s a best practice to confirm that you have all of the data you’re expecting before jumping right in to process it. So, we’ll take advantage of that to show one of three 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.

Step 3a: New Get Records: Find related Contact record

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 ContactRecordID resource that we set up in Step 2a to find the full record details and make them available later in the flow.

Using the ContactRecordID variable to find and store the full Contact record

  • Label: Find Related Contact Record
  • Description: Look up the Contact record for the ID that was passed into the ContactRecordID variable
  • Get Records of This Object: Contact
  • Filter Contact Records: All Conditions are Met (AND)
  • Filter Contact Records: 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

Step 3b: Decision: Do we have a contact record at all? With or without a photo?

For this example, we want the user to see a different message once they’ve successfully uploaded their photo. Because it’s not relevant for this blog, 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.

Using the new text field Photo CV ID that we set up in Step 1 to help us easily determine whether or not we have what we’re looking for

Note that you can use a simple checkbox, look for the presence of data in the Photo field, or use 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.

Decision part 1: We have a Contact record and it already has a photo.
Outcome 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’re 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.

Look for the record related to Find_Related_Contact_Record, or whatever you named your Get Records in Step 3a.

    • Just erase the trailing period so that it says {!Find_Related_Contact_Record} to reference the entire variable.

Your condition should look like the image: {!Find_Related_Contact_Record} with no period

  • Set up the second condition: Contact from Find_Related_Contact_Record.Photo_CV_ID__c [is null] False
    • In this condition, we’re asking the simpler question of whether or not the Photo CV ID on the Contact record is empty.
    • Follow the same steps as the first condition, but this time look for the Photo CV ID field when prompted for a field selection.

Outcome 2: Nearly identical, except we want to detect that the Photo CV ID is empty (or no photo exists) this time.
Click + next to Outcome Order to add Outcome 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
    Set up this condition exactly as you did for Outcome 1.
  • Set up the second condition: Contact from Find_Related_Contact_Record.Photo_CV_ID__c [is null] True
    Set up this condition exactly as you did for Outcome 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 Default Outcome and change the label to “No” so that if neither of our outcomes are true, we get an error screen.

Step 3c: Add element: Screen under “Yes — With Photo”

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.

The completed, very simple confirmation that we already have a photo for this person

  • 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.”

Step 3d: Add element: Screen under “No”

To keep 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 now and knock out the other quick screen before moving ahead.

The finished result of a simple Contact Error Display Text screen under the “No” (default) decision

  • 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 the box for “Show Header”.
  • Optional: Configure Footer: Uncheck the box for “Show Footer”.

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.”

Step 3e: Add element: Screen under “Yes — No Photo”

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 explained in the steps below.

When you set this up exactly as shown below, you’ll 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 importantly, it will give us back the Content Version ID of the newly-uploaded file so that we can process it in Step 4.

Screen properties for our File Upload screen with Header and Footer configured

  • 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 the box for “Show Header” and (optional) “Show Help Text”.
  • Configure Footer: Check the box for “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.

File Upload component with the settings shown in the right-hand part of the screen

  • 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 enter some sort of help text 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’ve left this to “Always”.

IMPORTANT: Expanding the Advanced dialogue box

The Advanced options for File Upload are the same as the standard ones, but you must set the Content Version IDs here instead of in the standard settings for this example to work

  • 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 the box 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’s put into the Contact rich text field. It’s a little unintuitive if you’re not a programmer, but, basically, even though we’re 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.

The portion of the flow we are working with in Step 4, directly under the Upload Your Photo screen

Step 4a: Loop: Process Content Version IDs

As stated in Step 4, even though we know we’re 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.

 Setting up a loop to process the UploadedCVIds from the file uploader

  • 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

Step 4b: Assignment: Assign CVID variable

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.

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. Assign the value that’s in whatever loop of the collection we’re on; remember, have only one for the CVID resource—that’s the only way this works.

Assigning the current item from the loop’s value to the CVID variable

  • Click + under the loop you added in Step 4a.
  • Choose Assignment.
  • Label: Assign CVID variable
  • Description: This sets the CVID variable which is used on the Contact record and to look up the file type
  • 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

Step 4c: New Get Records: Get CV from ID

While we have, in hand, the ID of the Content Version, we don’t have any other data about it. So, we have to 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’re 100% sure you’re allowing ONLY ONE file. Otherwise, you risk performing queries in a loop, which is never okay in Salesforce (or anywhere else, really).

Finding the Content Version record for the CVID in this loop

  • 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: 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 TypeOfFile resource you set up in Step 2d.
  • Field: File Type
  • Variable: TypeOfFile

Step 4d: Assignment: Set photo fields on the Contact record

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 we’re working with, which we selected at the very start of our flow in Step 3a.

  • Click + on the “After Last” line.

Where to add the Assignment in the "After Last" line

Add this step on the “After Last” line by clicking the + indicated by the arrow then choosing Assignment. Set the assignments for the Contact record that started our flow

  • 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.

Step 4e: Update record: Update Contact

This is 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.

 Update Contact uses the record we’ve been working with this whole time and is the last step in the flow setup.

  • 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.

Step 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.

Step 5a: Open your Contact Detail page in the Builder and add a Flow component

In Experience Cloud Builder, on Pages, we’re working with a Salesforce object page called Record Detail Layout.

Our page has a custom tab set up, and I created a new tab just for this flow called “Photo”. I’ve embedded a Flow component on this tab, as shown in the screenshot below.

Dragging the Flow component into the page canvas

Step 5b: Configure the Flow component

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 into your screen flow.

Configuring the Flow component to pass the record ID into the input variable

  • Click 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 to 4)
  • Check the box for “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 new Flow-based 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

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!

Resources

Want to see more good stuff? Subscribe to our channel!

SUBSCRIBE TODAY
How I Solved It with Jennifer Lee and Dee Ervin

Search Unsearchable Field Data Types | How I Solved It

Welcome to another “How I Solved It.” In this series, we do a deep dive into a specific business problem and share how one Awesome Admin chose to solve it. Once you learn how they solved their specific problem, you’ll be inspired to try their solution yourself! Watch how Dee Ervin searched unsearchable field data […]

READ MORE