<apex:page controller="New_Sales">
<apex:sectionHeader title="New SALES for {!forObject} {!forName}">
    <apex:form id="TheForm">
        <apex:pageBlock title="Select SALES Type then click Go:" id="ThePageBlock">
            <apex:pageBlockButtons location="bottom">
                <apex:commandButton value="Cancel" immediate="true" action="{!cancel}" />
                <apex:commandButton value="Go" action="{!loadAndGo}" rerender="ThePageBlock" />
            <apex:selectList value="{!selRecTypeId}" multiselect="false" size="1">
                <apex:selectOptions value="{!RecordTypes}"/>
            <apex:messages />
        <apex:pageBlock title="Available SALES Record Types:" id="TheLegendBlock">
            <apex:outputPanel title="Available SALES Record Types">
                <table style="border:1px solid black">
                    <apex:repeat value="{!recTypes}" var="rt">
                        <tr style="border:1px solid black">
                            <td style="border:1PX solid black">
                                <apex:outputField value="{!rt.Name}" />
                            <td style="border:1PX solid black">
                                <apex:outputField value="{!rt.Description}" /></td>

So above when you open the VF page it opens a screen that shows a list of record types, i'm able to get to this page in lightning using a component to urlEvent.fire() to it, also i'm able to get their via URL custom buttons...anyways you pick a record type and hit "Go" which should go to a record form to fill out, with some things pre-filled.

when you hit "go" it calls on an apex method loadAndGo, here's the entire class, but if you scroll down you'll see a


some code <---here's the method


    public class New_Sales {

    // Field definitions accessible by all methods in this class only:
    private Id parentId {get; set;}    
    private SObjectType sot {get; set;}
    private Account acc {get; set;}
    private Opportunity opp {get; set;}
    private Id acctId {get; set;}
    private string acctName {get; set;}
    private Id oppId {get; set;}
    private string oppName {get; set;}
    private Id campId {get; set;}
    private string campName {get; set;}
    private date oppCRDD {get; set;}
    private date oppCloseDate {get; set;}
     private string fldID {get;set;}
    private string fldName {get;set;}

    // To pass default values to a standard detail page, the URL for the detail page must be loaded with parameters constructed as follows:
    // ?CK + FieldID for Name/Text field or ?CK + FieldID + _lkid for lookup field.
    // Name fields must be passed with their lookup field counterparts when lookup fields are loaded.
    //  (Name field is loaded into the page, ID into the object)
    // The ToolingQry class gets the FieldIDs via the Tooling API for all URL parameters (Field IDs not available in schema describes)
    // The following field definitions will be loaded with the Field IDs for the URL Parameters we wish to load on the SALES detail page:
    private string fldID_Acct {get;set;}
    private string fldID_Opp {get;set;}
    private string fldID_Camp {get;set;}
    private string fldID_CRDD {get;set;}
    private string fldID_CloseDate {get;set;}

    // Field definitions accessible by all methods in all classes:
    public list<RecordType> recTypes {get; set;}
    public ID selRecTypeID {get ;set;}
    public string forObject {get; set;}
    public string forName {get; set;}
    public string newURL {get; set;}
    public Boolean isOpp {get; set;}

    public New_Sales() {
        // figure out from which object (Acct or Opp) we are creating the new Sales record:
 >>>>  parentId = ApexPages.currentPage().getParameters().get('pId');

        system.debug('parentId = ' + parentId);

        sot = parentId.getSObjectType();
        if(sot == Opportunity.sObjectType) {
            isOpp = true;
            // Get opp data for defaulting the new Sales fields:

            opp = [select Id, Name, AccountId, Account.Name, Marketing_Campaign__c, Marketing_Campaign__r.Name, CloseDate, Install_Date__c
                   from Opportunity where ID = :parentId AND  RecordTypeID NOT IN :CommonObjectRecordTypeUtil.opportunityRecordTypeList];

            forObject = 'Opportunity';
            forName = opp.Name;
            oppId = opp.Id;
            oppName = opp.Name;
            oppCRDD = opp.Install_Date__c;
            oppCloseDate = opp.CloseDate;
            acctId = opp.AccountId;
            acctName = opp.Account.Name;
            campName = '';
            if(opp.Marketing_Campaign__c != null) {
                campId = opp.Marketing_Campaign__c;
                campName = opp.Marketing_Campaign__r.Name;
        } else {
            isOpp = false;
            // Get acct data for defaulting the new Sales fields:
            acc = [select Id, Name from Account where ID = :parentId];
            acctId = acc.Id;
            acctName = acc.Name;
            forObject = 'Account';
            forName = acc.Name;

        // Load Record Types:
        // Load set of record type IDs available to running user and add to SOQL where clause . . .
        // recTypes = [Select Id, Name, DeveloperName, Description FROM RecordType where SobjectType = 'Sales_Toolkit__c' and isActive = true];
        set<Id> rtIDs = new set<Id>();
        list<RecordTypeInfo> rtis = Sales_Toolkit__c.SObjectType.getDescribe().getRecordTypeInfos();
        for(RecordTypeInfo rti : rtis) {
            if(rti.isAvailable()) rtIDs.add(rti.getRecordTypeId());
        recTypes = new list<RecordType>();
        for(RecordType rt : [Select Id, Name, DeveloperName, Description FROM RecordType
            where SobjectType = 'Sales_Toolkit__c' and isActive = true and Id in :rtIDs]) {

        // Load field IDs for URL construction using the ToolingQry class:

        // First get object ID:
        //  Remove white space & replace with + signs to avoid encoding in Tooling_Qry:
        string q = 'Select+Id+From+CustomObject+Where+DeveloperName=\'Sales_Toolkit\'';
        ToolingQry tq = new ToolingQry();
        list<map<string,string>> mapSTK = tq.tQry(q);

          system.debug('mapSTK = ' + mapSTK);
        // Next get Field IDs:
        // Remove white space & replace with + signs to avoid encoding in Tooling_Qry:
        q = 'Select+Id,+DeveloperName+From+CustomField+Where+TableEnumOrId=\'' + mapSTK[0].get('Id') + '\'';
        list<map<string,string>> listMapCFs = tq.tQry(q);

        // Now roll through the field/value maps & load field IDs:
        for(map<string,string> flds : listMapCFs) {
            fldId = '';
            fldName = '';
            for(string mapKey : flds.keySet()) {
                if(mapKey == 'Id') fldId = string.ValueOf(flds.get(mapKey)).left(15);
                if(mapKey == 'DeveloperName') fldName = string.ValueOf(flds.get(mapKey));
            if(fldName == 'Account') fldId_Acct = fldId;
            if(fldName == 'Opportunity_Name') fldId_Opp = fldId;
            if(fldName == 'Campaign_Lookup') fldId_Camp = fldId;
            if(fldName == 'Close_Date') fldId_CloseDate = fldId;
            if(fldName == 'SALES_CRDD') fldId_CRDD = fldId;

        // Override for tests only: (Temporarily run this code at all times until authentication issue in ToolingQry is fixed)
        if(Test.isRunningTest()) {
            if (fldID_Acct == null) fldID_Acct = '00Na000000BA8E4';
            if (fldID_Opp == null) fldID_Opp = '00Na000000BA8EA';
            if (fldID_Camp == null) fldID_Camp = '00N1400000BBufi';
        //system.debug('AcctID = ' + fldID_Acct);
        //system.debug('OppID = ' + fldID_Opp);
        //system.debug('CampID = ' + fldID_Camp);


    // Load selection options:
    public List<selectOption> getRecordTypes() {
        List<selectOption> rTypes = new List<selectOption>();
        for (RecordType rt : recTypes) rTypes.add(new selectOption(rt.Id, rt.Name));
        return rTypes;

    // Load record type legend
    public List<RecordType> getRecTypes() {
        return recTypes;
    public pageReference loadAndGo() {
        // Error message if no record type selected:
        pageReference goPage = null;
        if(selRecTypeID == null) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,'You must select a Sales type before clicking Go.'));
        } else {
            // URL Encode the account ID & Name:
            string urlAcctId = EncodingUtil.urlEncode(acctId, 'UTF-8');
            string urlAcctName = EncodingUtil.urlEncode(acctName, 'UTF-8');

            // Build URL passing account defaults as parameters:
            newURL = '/a4W/e?&RecordType=' + selRecTypeId + '&retURL=%2F' + parentId
                //+ '&ent=01Ia0000002HmTJ' + '&CF00Na000000BA8E4_lkid=' + urlAcctId + '&CF00Na000000BA8E4=' + urlAcctName;
                + '&ent=01Ia0000002HmTJ' + '&CF' + fldId_Acct + '_lkid=' + urlAcctId + '&CF' + fldId_Acct + '=' + urlAcctName;

            // Add Opportunity parameters to URL if parentID is opportunity:
            if(isOpp) {
                // URL Encode the opportunity ID & Name:
                string urlOppId = EncodingUtil.urlEncode(oppId, 'UTF-8');
                string urlOppName = EncodingUtil.urlEncode(oppName, 'UTF-8');
                string urlOppCRDD = string.valueOf(oppCRDD.month()) + '/' + string.valueOf(oppCRDD.day()) + '/' + string.valueOf(oppCRDD.year());
                urlOppCRDD = EncodingUtil.urlEncode(urloppCRDD, 'UTF-8');
                string urlOppCloseDate = string.valueOf(oppCloseDate.month()) + '/' + string.valueOf(oppCloseDate.day()) + '/' + string.valueOf(oppCloseDate.year());
                urlOppCloseDate = EncodingUtil.urlEncode(urlOppCloseDate, 'UTF-8');

                //Add Opp ID & Name to URL:

                newURL = newURL + '&CF' + fldId_Opp + '_lkid=' + urlOppId + '&CF' + fldId_Opp + '=' + urlOppName
                    + '&' + fldId_CloseDate + '=' + urlOppCloseDate + '&' + fldId_CRDD + '=' + urlOppCRDD;

                // Add Campaign parameters to URL if campaign name not null:
                if(campName != '') {
                    // URL Encode the opportunity ID & Name:
                    string urlCampId = EncodingUtil.urlEncode(campId, 'UTF-8');
                    string urlCampName = EncodingUtil.urlEncode(campName, 'UTF-8');

                    //Add Campaign ID & Name to URL:
                    //newURL = newURL + '&CF00N1400000BBufi_lkid=' + urlCampId + '&CF00N1400000BBufi=' + urlCampName;
                    newURL = newURL + '&CF' + fldId_Camp + '_lkid=' + urlCampId + '&CF' + fldId_Camp + '=' + urlCampName;

            // Now redirect to new URL:
            system.debug('URL = ' + newURL);
            pageReference newPage = new pageReference(newURL);
            goPage = newPage;

        return goPage;

    public PageReference cancel() {
        // cancel this new Sales request
        PageReference goBack = new PageReference('/' + ParentId);
        return goBack;


So in classic everything works fine, in lightning it'll work only to the first page when i select the record type, when i hit load and go i get an Invalid id: undefined.

the fatal error occurs on FATAL_ERROR Class.New_Sales.: line 62, column 1

parentId = ApexPages.currentPage().getParameters().get('pId');

Which is that line of code, you'll see it next to the >>>>>>>. Now here's the rub, when i did the ol system.debug the parentId was the record Id it was supposed to be. So then i thought "maybe it's the URL redirect thats wrong" so i checked it, on the same record (in both classic and lightning) the newURL is the same, but when the redirect happens in lightning it fails and that error pops up.

I also just copy pasted the system.debug of the new URL directly into classic, it worked, but in lightning i got the same error "invalid id: undefined".

the url redirect takes you to this


So i'm guessing it's the URL format....but one fun fact, on the

parentId = ApexPages.currentPage().getParameters().get('pId');

I decided, for testing, to simply do this

parentId = "<recordId i'm testing>";

Yes i decided to hardcode it, but all that does is gives me a blank blue lightning experience screen (works in classic).

I'm assuming the structure of the URL redirect is wrong....unless it something else that wrong? If the former are their any resources that exist that go over how to construct a redirect for lightning, such as the one i'm doing? I.E. prepop field data via a url redirect. My googlefuu is failing me in regard to finding any solutions.

EDIT: so it looks like I'll have to use sforce.one.createRecord. But if i have the {!loadAndGo} call JS function, that will do a (pseudocode)

 if( $User.UIThemeDisplayed = lightning/mobile) { 

    call apex method to return a needed array of strings (such as the variables defined in the apex controler selRecTypeId, parentId, fldId_Acct) needed to fill the prepop fields();

    sforce.one.createRecord('SALES',(variable from array),{ 
        FirstName : "var from array",
        LastName : "var from array",
        Custom_Field1__c : "var from array",
        Custom_Field2__c :"var from array",
} Else { Do old hacky method()}

1 Answer 1


Navigating to Url like:-


is URL hack which will not work in Lightning. As you are using the visualforce page, I suggest using sforce.one object which works in Lightning desktop and mobile.

createRecord(​entityName​[, recordTypeId][, defaultFieldValues])

Opens the page to create a record for the specified entityName, for example, “Account” or “MyObject__c”. recordTypeId is optional and specifies the record type for the created object. Calling createRecord without providing a recordTypeId may result in an error.

defaultFieldValues is optional and, if provided, prepopulates fields on a record create panel, including fields not displayed on the panel. Users must have create access to fields with prepopulated values. Errors during saving that are caused by field access limitations don’t display error messages.

        AccountId : "0010Y000007uaVI",  

Reference:- sforce.one

  • Will sforce.one.createRecord work classic or will i have to (pseudo code) If (ui= lightning) {do sforce.one method} else {do the hacky method i already have}. If the latter can VF page buttons have split functionality IE if running in lightning call this method else call this other method?
    – ensir
    May 31, 2019 at 17:29
  • now that i think about it i'll have the button call a embedded JS function to determine the theme being used, using $User.UIThemeDisplayed and if it's Not classic 2010 then call the lightning/mobile compatible methods
    – ensir
    May 31, 2019 at 17:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .