Convert Flow to Apex

(Can we really convert Flow to Apex code?)


Thanks for the heads up Nicolas and also thanks to Mohith Shrivastava who suggested I just use screen shots of the flow and not bother converting the flow into an XML file.

I’ve updated this post to include both tips and now we see an Apex trigger with A Trigger Handler, generated from screen shots of the flow (as well as the XML file example).

Do you really need flows?

This question was asked on LinkedIn by Azlam Abdulsalam and started my curiosity:

If an LLM model can write code and visualize it using mermaid, plantuml etc.

That got me thinking …

Do we really need Apex?

Could ChatGPT take a flow and convert it into Apex for me?

I can’t write Apex but I can write a good flow, can ChatGPT help me with this?

I asked Mohith Shrivastava, Principal Developer Advocate at Salesforce | AI Agents & Apps.

I don’t think he was calling me stupid so I thought it was worth a shot!

A Sample Flow to Test

I had a simple record-triggered flow that I could use to see if it is possible to convert a Flow to Apex.

It just sets the Account rating field if the type = Customer – Direct and the Industry is Finance (where the Rating value is not already set).

The Start criteria is as follows:

The record is updated to Hot:

Enter ChatGPT

I need to feed this flow into ChatGPT somehow.

As it turns out there are a couple of options:

  • Feed it screenshots of the flow (yes just images!)
  • Feed it an XML file for the flow

Using Screenshots

Now this was a real surprise to me – you can use screen shots of a flow to create an Apex Trigger.

So I said to ChatGPT: (also taking into account Nicolas’s comments about trigger frameworks):

and I uploaded the 3 images:

The output from ChatGPT was as follows:

Based on the provided Salesforce Flow images, I will convert this Flow into an Apex Trigger using a Trigger Framework for best practices. The Flow is a Before-Save trigger that updates the Rating field on Account records when:

  • Rating is NULL
  • Type is Customer – Direct
  • Industry is Finance

The trigger will follow the Trigger Handler pattern, ensuring scalability and maintainability.


Step 1: Apex Trigger (AccountTrigger)

trigger AccountTrigger on Account (before insert, before update) {
    if (Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)) {
        AccountTriggerHandler.updateAccountRatings(Trigger.new);
    }
}

Step 2: Trigger Handler (AccountTriggerHandler.cls)

public class AccountTriggerHandler {
    public static void updateAccountRatings(List<Account> accountList) {
        for (Account acc : accountList) {
            if (acc.Rating == null && acc.Type == 'Customer - Direct' && acc.Industry == 'Finance') {
                acc.Rating = 'Hot';
            }
        }
    }
}

Step 3: Apex Test Class (AccountTriggerHandlerTest.cls)

@isTest
public class AccountTriggerHandlerTest {
    @isTest
    static void testUpdateAccountRatings()
        // Create a test account matching the Flow's criteria
        Account testAccount = new Account(
            Name = 'Test Finance Customer',
            Type = 'Customer - Direct',
            Industry = 'Finance',
            Rating = null
        );
        
        // Insert Account
        insert testAccount;
        
        // Query the inserted account
        Account insertedAccount = [SELECT Id, Rating FROM Account WHERE Id = :testAccount.Id];
        
        // Verify that the Rating is updated to 'Hot'
        System.assertEquals('Hot', insertedAccount.Rating);
        
        // Test case where the criteria are not met
        Account nonMatchingAccount = new Account(
            Name = 'Test Non-Finance Customer',
            Type = 'Customer - Direct',
            Industry = 'Technology', // Does not match "Finance"
            Rating = null
        );
        insert nonMatchingAccount;
        // Query the non-matching account
        Account queriedAccount = [SELECT Id, Rating FROM Account WHERE Id = :nonMatchingAccount.Id];
        // Verify that the Rating remains null
        System.assertEquals(null, queriedAccount.Rating);
    }
}

Summary:

  • The Trigger calls a separate Trigger Handler for better maintainability.
  • The Handler updates the Rating field if the conditions match.
  • The Test Class ensures that the trigger works correctly with both matching and non-matching records.

This approach follows Salesforce best practices and ensures scalability. 🚀 Let me know if you need any modifications!

Wow, that impressive!



Using the Flow XML

Here was the original way I tried, using Salesforce Inspector Reloaded to generate an XML file from my flow.

Click the little icon to open the inspector and then select Download Metadata:

Untick the Select All option and just choose Flow (1) and then click Download metadata (2).

When finished, click the See downloaded metadata link:

The results are contained in a zip file a couple of folders deep.

Metadata.zip -> unpackaged -> flows

Navigate down until you find the flow and copy the file.

Navigate back to a higher folder (eg downloads) and paste the copy there.

This will extract it from the zip file making an uncompressed file that you can easily access.

Now we need to instruct ChatGPT:

Got it! Please upload the XML file, and I’ll convert the Salesforce Flow into Apex for you.


?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>57.0</apiVersion>
    <description>Account update rating</description>
    <environments>Default</environments>
    <interviewLabel>Account-UpdateRating {!$Flow.CurrentDateTime}</interviewLabel>
    <label>Account-UpdateRating</label>
    <processMetadataValues>
        <name>BuilderType</name>
        <value>
            <stringValue>LightningFlowBuilder</stringValue>
        </value>
    </processMetadataValues>
    <processMetadataValues>
        <name>CanvasMode</name>
        <value>
            <stringValue>AUTO_LAYOUT_CANVAS</stringValue
        </value>
    </processMetadataValues>
    <processMetadataValues>
        <name>OriginBuilderType</name>
        <value>
            <stringValue>LightningFlowBuilder</stringValue>
        </value>
    </processMetadataValues>
    <processType>AutoLaunchedFlow</processType>
    <recordUpdates>
        <description>Update rating on account when Type = Customer - Direct and Industry = Finance</description>
        <name>Update_Account</name>
        <label>Update Account</label>
        <locationX>176</locationX>
        <locationY>311</locationY>
        <inputAssignments>
            <field>Rating</field>
            <value>
                <stringValue>Hot</stringValue>
            </value>
        </inputAssignments>
        <inputReference>$Record</inputReference>
    </recordUpdates>
    <start>
        <locationX>50</locationX>
        <locationY>0</locationY>
        <connector>
            <targetReference>Update_Account</targetReference>
        </connector>
        <doesRequireRecordChangedToMeetCriteria>true</doesRequireRecordChangedToMeetCriteria>
        <filterLogic>and</filterLogic>
        <filters>
            <field>Rating</field>
            <operator>IsNull</operator>
            <value>
                <booleanValue>true</booleanValue>
            </value>
        </filters>
        <filters>
            <field>Type</field>
            <operator>EqualTo</operator>
            <value>
                <stringValue>Customer - Direct</stringValue>
            </value>
        </filters>
        <filters>
            <field>Industry</field>
            <operator>EqualTo</operator>
            <value>
                <stringValue>Finance</stringValue
            </value>
        </filters>
        <object>Account</object>
        <recordTriggerType>CreateAndUpdate</recordTriggerType>
        <triggerType>RecordBeforeSave</triggerType>
    </start>
    <status>Active</status>
</Flow>


Here’s an equivalent Apex trigger to replace your Salesforce Flow.

This trigger updates the Rating field on Account records before they are saved, based on the conditions specified in your Flow.

trigger AccountUpdateRating on Account (before insert, before update) {
    for (Account acc : Trigger.new) {
        // Check if Rating is null, Type is 'Customer - Direct', and Industry is 'Finance'
        if (acc.Rating == null && acc.Type == 'Customer - Direct' && acc.Industry == 'Finance') {
            acc.Rating = 'Hot';
        }
    }
}

Explanation:

  • The trigger runs before insert and before update (equivalent to a before-save flow).
  • It loops through the Trigger.new list (all records being processed).
  • It checks:
    • If Rating is null
    • If Type is 'Customer - Direct'
    • If Industry is 'Finance'
  • If all conditions are met, it updates Rating to 'Hot'.

Absolutely! Here’s a test class to ensure the trigger works correctly:

Test Class:

@isTest
public class UpdateAccountRatingTest {
    @isTest
    static void testUpdateAccountRating() {
        // Create an Account that meets the trigger criteria
        Account acc = new Account(
            Name = 'Test Account',
            Type = 'Customer - Direct',
            Industry = 'Finance',
            Rating = null // This should be updated by the trigger
        );
        // Insert the account - trigger should fire
        insert ACC;
        // Retrieve the inserted record
        Account insertedAcc = [SELECT Id, Rating FROM Account WHERE Id = :acc.Id];
        // Verify the Rating field was updated correctly
        System.assertEquals('Hot', insertedAcc.Rating, 'Rating should be set to Hot');
        // Now, test an update scenario
        Account accToUpdate = new Account(
            Id = insertedAcc.Id,
            Type = 'Customer - Direct',
            Industry = 'Finance',
            Rating = null // Simulating Rating being reset to null
        );
        // Update the account
        update accToUpdate;
        // Retrieve the updated record
        Account updatedAcc = [SELECT Id, Rating FROM Account WHERE Id = :accToUpdate.Id];
        // Verify the Rating field was updated correctly again
        System.assertEquals('Hot', updatedAcc.Rating, 'Rating should be set to Hot after update');
        // Negative Test Case - Account not meeting criteria
        Account accNegative = new Account(
            Name = 'Test Account 2',
            Type = 'Customer - Indirect', // Different Typ
            Industry = 'Finance',
            Rating = null
        );
        // Insert the account
        insert accNegative;
        // Retrieve the inserted record
        Account insertedAccNegative = [SELECT Id, Rating FROM Account WHERE Id = :accNegative.Id];
        // Verify the Rating field was NOT updated
        System.assertEquals(null, insertedAccNegative.Rating, 'Rating should remain null for non-matching records');
    }
}

What This Test Class Does:

✅ Inserts an Account meeting the trigger conditions and verifies that Rating = 'Hot'.
✅ Updates an Account with Rating = null and ensures the trigger fires again.
✅ Tests a negative case where the account does not meet criteria (Type ≠ “Customer – Direct”).

This test ensures that your trigger works as expected and covers both positive and negative scenarios. 🚀

Let me know if you need modifications! 😃

Create the Apex Trigger File

To create our Apex Trigger in our Salesforce org, follow these steps:

Open the Developer console:

Select New -> Apex Trigger:

Give the trigger a name:

A default trigger file is created:

Remove the highlighted text and paste in our new trigger as created by ChatGPT:

Save the new trigger

Create the Test Class

Now, create the test class file (needed to migrate this file to production and meet the test coverage requirements):

Give it a name:

Paste in the test class as generated by ChatGPT and then save the file:

Remember to save the file.

Now select Test and choose New Run to execute a test.

Select the Test Class (1) and then the test within it (2) and click Run.

Click the AccountUpdateRating.apxt tab and then the small arrow circled here to see the test coverage.

Before testing the trigger – deactivate your flow or you will have two automations running on the account and it will be confusing.

Now create a new Account or find one with an empty Rating. Set the Type to Customer – Direct and the Industry to Finance.

Save the records and the trigger should now run and set the Rating to Hot as shown below.

Winning!!

What about Process Builder?

Next, I told my buddy Tim Combridge about it – he asked would it work for a Process Builder.

Under the covers, a process builder is like a really simple flow with a similar internal structure so I was reasonably confident it may just work.

Luckily I had an old one hanging around and …..

YES it worked.

What about a Screen Flow?

Next, I thought what about a Screen Flow? Could it take a screen flow and make a Lightning Web Component from that?

I have a screen flow that uses a table to display Accounts with a Record Type = Retail.

so I told ChatGPT:

I then generated the XML using the Salesforce Inspector Reloaded, pasted it in to ChatGPT and it successfully created all the parts of a lightning Web Component:

  • An ApexController
  • LWC Javascript
  • LWC HTML
  • LWC Metadata XML
  • A Test Class

Cautions:

  1. For a trigger, include the words “please convert to an apex trigger using a trigger framework to meet best practice.”
  2. Don’t rely on this method to generate perfect code, always get an experienced developer to review the code.

While I realize this does not make me an Apex Developer, it could help me learn how to create Apex triggers and classes from my existing flows. If I was an experienced Apex developer it may be a good tool to convert some flows over to Apex to gain better performance.


Similar Posts