Load a Visualforce Page with Parameters in Lightning

I was looking to display Visualforce pages using the RecordId inside of my Lightning Record pages. After finding a very elegant solution to pass the RecordId from a Lightning Component container to a Visualforce page displayed in an iframe I decided it was time to move the solution from the Sandbox into Production.

What worked perfectly in the Sandbox gave me nothing but a blank component in Production. After spending way too much time trying to find a solution, I gave up and went to Plan B. (edit: my other component works once I adjusted my clickjack settings)


Plan B

I was able to use a Lightning Component to navigate to a URL, so I built a component to build the URL for the Visualforce page including the parameter for the RecordId of the page.

    doInit : function(component, event, helper) {
        var params = new Array();
        params.push(component.get('v.recordIdVar') + '=' + component.get('v.recordId'));
        component.set('v.queryString', params.join('&'))

    openPage : function(component, event, helper) {

        // Show spinner once button is pressed
        var spinner = component.find('spinner');
        $A.util.removeClass(spinner, 'slds-hide');

        // Build url for Visualforce page
        var vfURL = 'https://' + component.get("v.domain") + '--c.visualforce.com/apex/';
        vfURL = vfURL + component.get("v.pageName") + '?';
        vfURL = vfURL + component.get("v.queryString");
        console.log("VF URL: ",vfURL);

On a standard Lightning page, the component switches from the Record page to the Visualforce page, returning to the Record page when the Visualforce page exits.

                // Handle non-console user
                console.log("Not in Console");
                var urlEvent = $A.get("e.force:navigateToURL");
                     "url": vfURL

It is a bit different on a Console page where the Visualforce Page is loaded in a new Tab which reverts to the Record page Tab when exiting. This normally leaves a second “dead” Record page Tab behind so I made sure the component saved the ID of the Calling Tab or Subtab and closed the “dead” tab when finishing with the Visualforce page.

// Check to see if running in a Console
    var workspaceAPI = component.find("workspace");
    workspaceAPI.isConsoleNavigation().then(function(consoleResponse) {            
        console.log("IsConsole: ", consoleResponse);
        if (consoleResponse) {

            // Save current tab info
            workspaceAPI.getFocusedTabInfo().then(function(tabResponse) {
                var closeTabId = tabResponse.tabId;
                var closeTitle = tabResponse.title;
                var parentTabId = tabResponse.parentTabId;
                var isSubtab = tabResponse.isSubtab;
                console.log("Current Tab: ", closeTabId + " | " + closeTitle);
                console.log("Is Sub: ",isSubtab," ParentId: ",parentTabId);

                // Open Visualforce Page in a new tab
                if (isSubtab) {
                        parentTabId: parentTabId,
                        url: vfURL,
                        focus: true
                    }).then(function(openSubResponse) {
                        console.log("New SubTab Id: ", openSubResponse);
                    .catch(function(error) {
                } else {
                        url: vfURL,
                        focus: true
                    }).then(function(openParResponse) {
                        console.log("New ParentTab Id: ", openParResponse);
                    .catch(function(error) {

                // Because exiting the VF page will reopen the object record,
                // close the tab we started on
                if (tabResponse.closeable && !tabResponse.pinned) {
                        tabId: closeTabId
                    }).then(function(closeResponse) {
                        console.log("Closed: ", closeTitle);                      
                    .catch(function(error) {
                } else {
                    console.log("Left Open: ", tabResponse.title);
            .catch(function(error) {

Rather than load the Visualforce page immediately, I display a button. On the button press, the component loads the Visualforce page.

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global">

    <lightning:workspaceAPI aura:id="workspace" />

    <aura:attribute name="buttonLabel" type="String" />
    <aura:attribute name="buttonVariant" type="String" default="neutral" />
    <aura:attribute name="domain" type="String" />
    <aura:attribute name="pageName" type="String" />
    <aura:attribute name="recordIdVar" type="String" />
    <aura:attribute name="otherParams" type="String" />

    <aura:attribute name="queryString" type="String" />

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

        <lightning:layoutItem padding="around-small">
            <lightning:button label="{!v.buttonLabel}" variant="{!v.buttonVariant}" onclick="{!c.openPage}" />
            <lightning:spinner aura:id="spinner" alternativeText="Loading" variant="brand" size="large" class="slds-hide" />


I’ve made the component as generic as possible by providing the following attributes one can use when placing the component on their Lightning Record page.

  • Button Label (Text to appear on the Button)
  • Button Variant (Button styling: base, neutral, brand, destructive, success)
  • Domain Name (Your Production or Sandbox domain name)
  • Visualforce Page Name (The API Name of the Visualforce Page)
  • RecordId Parameter Name (The name of the URL parameter for the RecordId)
  • Additional URL Parameters (Any other URL parameters & values)

Here’s the Component in Action

Notify of
Inline Feedbacks
View all comments
Kelly B

Hi there, I used this and setup in a Sandbox, and it works great. Moved it into Production, but ran in to the blank page issue you had mentioned, and I ended up having to add *.visualforce.com to the CSP Trusted Sites. Additionally, we’re now having issues getting this to run across a VPN, although a custom button that calls the VF page works on the VPN. Any ideas why that might be?

Bryan Hunt

I am having a similar issue. We have a Lightning Component button on one of our record page layouts. I am using the UrlEvent.fire() method in my component to invoke my VF page. My VF page (by design) never actually displays as it create an an .xls file that is downloaded to the user via the browser. All of that works fine, but like you I am then presented with a blank page showing my Sales App and all of its tabs, but nothing below the blue dividing line. I have tried several different methods of trying to redisplay the… Read more »