The basic question is this: can a VF controller update a variable and that new value be accessible in the flow? I had assumed that a variable defined by apex:param in a VF page would be accessible by both the Apex controller and the Flow as long as the Flow defined a input/output resource with the same name, but there doesn't seem to be any data-binding from the VF page to the Flow.

There are 3 parts to this: a flow with input/output resources, a Visualforce page using <flow:interview /> with <apex:param /> nodes, and a custom Apex controller. I am using the flow to upload documents as attachments to a custom Membership Application object. The flow is supposed to validate whether the document has been uploaded, then move on to the next document.

In the flow, I have resources for each document like:

  • API Name: PledgeFormUploaded
  • Type: Boolean
  • Available for input: checked
  • Available for output: checked

I also have formula resources

  • API Name: fxPledgeFormUploaded
  • Type: Boolean
  • Condition: {!PledgeFormUploaded} != null && {!PledgeFormUploaded} == true <=== this is where I am trying to referenced the newly updated value.

I am embedding the flow into a Visualforce page with a custom controller. (VF markup and apex below) The upload form renders fine. I have an Apex method that reads a variable in flow to determine whether the upload form should be rendered on the current flow screen. This works correctly. The flow has about 10 screens, and I only want the upload form on some of them.

After the document is uploaded, the user clicks the "Next" and there is a decision block that checks to see if the document has been uploaded.

enter image description here

The decision always follows the default choice. It is never "Yes" after the document has been uploaded.

<flow:interview name="Online_Membership_Application" interview="{!MyFlowRef}" FinishLocation="{!FinishLocation}" buttonLocation="bottom">
    <apex:param name="accountId" value="{!AccountId}"></apex:param>
    <apex:param name="PledgeFormUploaded" value="{!PledgeFormUploaded}"></apex:param>
    <apex:param name="LetterUploaded" value="{!LetterUploaded}"></apex:param>
    <apex:outputPanel layout="block" styleClass="file-upload" rendered="{!ShouldRenderFileUpload}">
        <apex:form enctype="multipart/form-data">
            <h4>File Upload</h4>
            <div class="file-upload-group">
                <div class="input-group">
                    <div class="input-group-btn">
                        <div class="btn btn-primary btn-file">
                            Browse&hellip; <apex:inputFile value="{!ApplicationAttachment.Body}" filename="{!ApplicationAttachment.Name}" contentType="{!ApplicationAttachment.ContentType}" />
                    <input type="text" class="form-control" readonly="readonly" />
                <span class="help-block">
                    <apex:outputText value="{!$Label.FileUploadHelp}" escape="false" />
            <apex:commandButton value="Upload" styleClass="btn btn-primary btn-sm" action="{!upload}"/>

Here is the controller code.

public without sharing class MembershipApplicationFlowController {
    private enum UploadFormType { NONE, PLEDGE, LETTER }

    private Boolean blnPledgeFormUploaded = false;
    private Boolean blnLetterUploaded = false;

    private static final Set<String> BLACKLISTED_FILETYPES = new Set<String> {
            'application/x-msdownload', 'application/bat', 'application/x-bat', 'application/octet-stream'

    private static final Set<String> BLACKLISTED_EXTENSIONS = new Set<String> {
            'scr', 'dll', 'cmd', 'bin', 'exe', 'bat'

    public Attachment ApplicationAttachment {
        get {
            if (ApplicationAttachment == null) {
                ApplicationAttachment = new Attachment();
            return ApplicationAttachment;

    public Flow.Interview.Online_Membership_Application MyFlowRef { get; set; }

    public Id getAccountId() {
        return CommunityUser.getAccountId();
    public Boolean getPledgeFormUploaded() {
        return blnPledgeFormUploaded;
    public Boolean getLetterUploaded() {
        return blnLetterUploaded;

    public PageReference getFinishLocation() { ... }

    public Id getApplicationId() {
        return MyFlowRef.MembershipApplication.Id;

    public Boolean getShouldRenderFileUpload() {
        UploadFormType formType = getUploadFormTypeFromFlow();
        return formType != UploadFormType.NONE;

    public PageReference upload() {
        ApplicationAttachment.ParentId = getApplicationId();
        String extension = '';
        List<String> fileParts = ApplicationAttachment.Name.splitByCharacterType();

        if (fileParts != null && !fileParts.isEmpty()) {
            extension = fileParts[fileParts.size() - 1];
        if (BLACKLISTED_FILETYPES.contains(ApplicationAttachment.ContentType) ||
                BLACKLISTED_EXTENSIONS.contains(extension)) {
            return null;

        try {
            insert ApplicationAttachment;
            UploadFormType formType = getUploadFormTypeFromFlow();
        } catch (DMLException e) {
            //error handling
        } finally {

        return null;

    private UploadFormType getUploadFormTypeFromFlow() {
        if (MyFlowRef == null) {
            return UploadFormType.NONE;
        else if (MyFlowRef.ShowUploadPledgeForm != null && MyFlowRef.ShowUploadPledgeForm) {
            return UploadFormType.PLEDGE;
        else if (MyFlowRef.ShowUploadLetter != null && MyFlowRef.ShowUploadLetter) {
            return UploadFormType.LETTER;
        return UploadFormType.NONE;

    private void setSuccessForUploadFormType(UploadFormType formType) {
        if (formType == UploadFormType.PLEDGE) {
            blnPledgeFormUploaded = true;
        else if (formType == UploadFormType.LETTER) {
            blnLetterUploaded = true;

    private void clearAttachment() {
        ApplicationAttachment = null;

At the end of a successful upload, the setSuccessForUploadFormType method sets the private variable to true, which is read by the getPledgeFormUploaded() method, which is read by <apex:param name="PledgeFormUploaded" value="{!PledgeFormUploaded}"></apex:param>. The flow has the input/output variable by the same name which is being read by the decision block. This is not working, but I do not know why.

The best I can come up with is that this has to do with the state of the form.. maybe the private variables are reset every time the page loads. I thought VF would hold that state if they were updated. But if that's the case, then I would have to incrementally update the membership application object after the upload, then reload the membership object in the flow between every document upload. Before I refactored my code and flow, I wanted to make sure I wasn't doing something wrong.

  • A thought just occurred to me.. I am not using a setter.. I am setting the private variable, but if there is binding going on behind the scenes in VF, then setting the private variable in the controller class might not be enough. Maybe it needs to be bound to a property, or have a setPledgeFormUploaded(Boolean) method. – jwatts1980 Mar 22 at 2:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.