Visual Workflow: Converting Leads with an Apex Plugin

The new Cloud Flow Designer, generally available as of the Spring ’12 release, makes creating, editing, and packaging flows easier than ever…and without having to install and maintain desktop software. The Cloud Designer makes it very easy to assemble flow screens, make branching decisions, assign values to variables, and lookup/create/update/delete records in Salesforce. But you may have noticed that something from the desktop designer is missing: the ability to convert leads. Don’t fret: with the Cloud Designer, there’s a way to do that!

Apex Code and Visual Workflow

Visual Workflow was designed to be complementary to the work you do in VisualForce and Apex. According to idealists (myself included), developers write atomic, general-purpose functionality, which business users can then plug into their declarative designs. And Visual Workflow is no exception. You can now expose your Apex code as a step in a Flow, with inputs and outputs!

We can use this capability to our advantage here. We know that Apex Code can easily convert a lead, just like it does in the regular Salesforce UI. Taking that code and exposing it as a step in a flow is as easy as adding a few lines of code.

The Sample Package

We’ve created a sample package that illustrates this concept. In your Salesforce Developer Edition org or Developer Sandbox, install the following package:

Visual Workflow – Sample – Convert Lead
My special thanks goes to Visual Workflow Product Manager Varadarajan Rajaram for providing the sample code.

This package contains two items: an Apex class (VWFConvertLead) and a sample Flow that uses the Apex. Drill into the VWFConvertLead class and have a look at the sample code.

The Process.Plugin Interface

If you want to make an Apex class available in Flow, that class must implement the Process.Plugin interface. This is the interface exposed in Apex that you must implement to allow a flow to call Apex. The interface has two methods you must implement: describe() and invoke().

The describe() Method

describe() tells Visual Workflow what data should be passed into the Apex step, and what data the step will return. The sample VWFConvertLead class does just that in the describe() method:

    // This method describes this plugin, as well as its inputs and outputs to the Cloud Designer
    // NOTE: Implementing this method is what makes this class appear in the designer
    global Process.PluginDescribeResult describe() {
        // Set up plugin metadata
        Process.PluginDescribeResult result = new Process.PluginDescribeResult();
        result.description = 'The LeadConvert Flow Plug-in converts a lead into an account and contact, as well as (optionally) an opportunity.';
        result.tag = 'Lead Management';

        // create a list that stores both mandatory and optional *input* parameters from a Flow
        // NOTE: Only primitive types (STRING, NUMBER, etc) are supported at this time.
        // Collections are not currently supported
        result.inputParameters = new List<Process.PluginDescribeResult.InputParameter>{
            // Lead ID (mandatory)
            new Process.PluginDescribeResult.InputParameter('LeadID',
                    Process.PluginDescribeResult.ParameterType.STRING, true),
            // Account Id (optional)
            new Process.PluginDescribeResult.InputParameter('AccountID',
                    Process.PluginDescribeResult.ParameterType.STRING, false),
            // Contact ID (optional)
            new Process.PluginDescribeResult.InputParameter('ContactID',
                    Process.PluginDescribeResult.ParameterType.STRING, false),

        // Create a list that stores *output* parameters sent *to* your flow.
        result.outputParameters = new List<Process.PluginDescribeResult.OutputParameter>{
            // Account ID of the converted lead
            new Process.PluginDescribeResult.OutputParameter('AccountID',

        return result;

Both input and output parameters are represented in this code, so that you can pass data into the Flow step, then use the data the class returns after the step executes. Some input and output parameters in the sample are mandatory, while others are optional.

Once you’ve successfully implemented all Process.Plugin methods in your class, it will show up in the Cloud Designer’s Palette tab:

Apex Plugin in the Cloud Designer Palette

Apex Plugin in the Cloud Designer Palette

A few things to note: This plug-in appears under the Lead Management section of the palette, and once selected in the palette the Description pane displays the plug-in description, the inputs, and the outputs that the step expects and generates. Your class can populate this information in the describe() method:

Process.PluginDescribeResult result = new Process.PluginDescribeResult();
result.description = 'Step Description';
result.tag = 'Lead Management';

The Cloud Flow Designer discovers all plug-ins and their describe() implementations, so using the plug-in metadata will help you organize and document the Apex plug-ins you create for your Flow editors.






The invoke() Method

This is where the hard work of converting a lead gets done. While the ins and outs of converting a lead with Apex code are beyond the scope of this post, it’s worth reviewing portions of this invoke() implementation to see examples of retrieving data inputs and populating data outputs from this step in a flow.

First, the class receives an instance of Process.PluginRequest as an input parameter, and returns a Process.PluginResult to the flow. Next, we retrieve the input parameters from the flow:

global Process.PluginResult invoke(Process.PluginRequest request) {
    String leadID = (String) request.inputParameters.get('LeadID');
    // ...
    return new Process.PluginResult(result);

Think about the amount of work you could get done with a class like this…then think about encapsulating that work, and giving your flow designers an easy, well-documented plug-in to do profound and powerful things!

About collections: Process.Plugin and Visual Workflow do not currently support collections, so doing something like retrieving a variable number of choices to populate radio buttons isn’t directly supported in Visual Workflow. There are alternative ways of accomplishing this…but that’s best left for another post.

Using the Plug-in

Let’s see this thing in action. Open the SAMPLE – Lead Convert Flow in the cloud designer and double click the Convert Lead step:

Note the Input and Output tabs. Click Add Row on the Input tab and review the optional parameters you can pass into the step. Remember those input and output parameters you defined as either optional or mandatory in the describe() method? Time to enjoy the fruit of your labors!

NOTE: You may choose to run this step in your Developer Edition org or Sandbox, but keep in mind that it will convert a lead if you step all the way through the flow.




So there you have it: using Process.Plugin provides a way to encapsulate powerful logic into a flow, encourages good developer practices, and can make you a hero to anyone responsible for a business process.

Have you used Process.Plugin in your flows? Leave a comment and tell us about your experience!






tagged , , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • ramanathan pachaiyappan

    “Process.Plugin and Visual Workflow do not currently support collections”,

    If flows won’t support collection, its going to cost more in terms of governor limit.

    • Possibly. It really depends on how you architect things. You can certainly use collections within a Process.Plugin step, but you can’t pass collections back and forth to and from a flow.

      • Can you give me a real brief description on how one could get around this? I even thought about creating a buffer object, but even then it would be a pain to mass delete the records used.

  • Interesting thought! In one sense you can think of VWF as a way to bypass governor limits you might’ve run into in Apex, but I can’t help but think that governor limits are more often a result of something missed in the architecture.

    In the case of a plugin, let me clarify: you can certainly use collections within the invoke() method of Process.Plugin. But currently you can’t pass collections into an Apex step, nor can you retrieve collections from an Apex step into your flow. But if the work you do in the invoke() method requires collections, that’s perfectly fine.

  • Jeff

    I get this error when trying to run the flow. I get this error no matter what value I put for convertedStatus:

    A fault occurred while executing your script:ConvertLead failed. First exception on row 0; first error: INVALID_STATUS, invalid convertedStatus: Closed – Converted: []

    • Anonymous

      did anyone ever figure out why this error occurs? im experiencing the same issue. any feedback would be appreciated. thank u

      • Jeff

        Yes, I figured it out, sorry for not updating my comment. You have to add “Closed – Converted” as a picklist value for the lead field “Lead Status.” Once you add this value the flow will work properly.

  • Adriana Trejo Vital

    The trouble I’m having is that anytime the lead name matches the name of an existing account, the plugin automatically adds the new contact and opportunity to the existing account instead of creating a new account with the same name. I would like the plugin to create a new account every time. Can this be something we can do?

    • Joe Morse

      It certainly can. The code is provided in the package, so that should allow you to alter it to work the way you want it to.

      One interesting note is that you could certainly alter the flow to prompt the user whether they want to dedupe or create a new account.

  • Anthony Caputa

    Hey Mr. Morse,

    Thanks for this article, I’ve been racking my brain for the last 6 months trying to figure out how to get the flow to do this as it is a necessity for our process. With out it we can’t use the flow. My question to you is, neither myself nor the other gentleman who work heavily with Salesforce for our company is very good with APEX. I see the package you have located here that we can download, if we download that package is it something we can use permanently or is it only meant as a sample and won’t function for extended periods of time or fully. If it’s not meant to be a permanent solution, is there something we can download that will be a permanent solution, or will we have to become more familiar with APEX in order to fix this problem.



    • Joe Morse

      Hi Tony. The package here is definitely meant as a sample that illustrates how one would use this. I definitely won’t vouch for it being production-ready 🙂 My advice is always to consult an Apex expert before deploying something like that to production.

      I hear you on learning Apex. The nice thing about low Plugins is that they’re fairly atomic…you can build a simple, bullet-proof plugin that does one or two key things, then use it again and again. That way, you can get a third party to write your important plugins on a one-time basis, then use them for a good long time.

      • Anthony Caputa

        Thanks for the info, I’m playing around with the Desk top Flow designer now, it looks like it has a lead convert button built into it so hopefully that will solve the problem. Thanks again.

        • Joe Morse

          I’d strongly suggest shying away from the desktop designer, as it’s been deprecated and will not create flows that are compatible with the cloud designer.

        • Joe Morse

          I’d strongly suggest shying away from the desktop designer, as it’s been deprecated and will not create flows that are compatible with the cloud designer.

  • Heiko Leibenath

    Hey Joe, long time no talk. I’ve installed the package in my DE org since I couldn’t get my own class to work but it looks like I have a general error: Whenever I try to invoke an APEX class from a VWF, I get the following error:

    A fault occurred while executing your script:

    Data Not Available: The data you were trying to access could not be found. It may be due to another user deleting the data or a system error. If you know the data is not deleted but cannot access it, please look at our support page.

    Any ideas?