Andy in the Cloud

From BBC Basic to Force.com and beyond…

How To: Call Apex code from a Custom Button

38 Comments

Screen Shot 2013-07-16 at 08.21.48‘How do I call Apex code from a Custom Button?’ is a simple question, however the answer is covered across various developers guides and general documentation…

Having answered a number of questions on StackExchange over the year or so I’ve been active on it, I thought I’d compile this how to guide as reference peace. Of course its littered with links to the  excellent Salesforce documentation, so please do dig into those as well.

The steps are as follows to add either a Detail or a List View  button (as illustrated below) to Standard or Custom Object. It’s well worth going through the topics and general reference guides I’ve linked in more detail. I’ve given some examples of my own, but there are also plenty of them in the help topics I’ve linked to if you need more examples.

Screen Shot 2013-07-16 at 09.13.00Screen Shot 2013-07-16 at 09.12.46

Steps to Create a Custom Button that runs Apex Code

  1. Create a Apex Extension Controller class as shown in the examples below.
  2. Create a Visualforce page, using the ‘standardController‘ and ‘extensions‘ attributes on apex:page *
  3. Create a Custom Button using the Visualforce page as its Content Source
  4. Add the Custom Button to the appropriate Layout of the object
  5. Use either the ‘action‘ attribute (see warning below) or apex:commandButton‘s on your page to invoke Apex logic.


*
 You must also use the ‘recordSetVar‘ attribute on apex:page if you wish to create List View button.

Detail Page Custom Button Template

Example page and class using apex:commandButton to invoke the logic.

<apex:page standardController="Test__c" extensions="DetailButtonController">
    <apex:form >
        <apex:commandButton value="Do something" action="{!doSomething}"/>
    </apex:form>
</apex:page>

Apex controller code.

public with sharing class DetailButtonController
{
    private ApexPages.StandardController standardController;

    public DetailButtonController(ApexPages.StandardController standardController)
    {
        this.standardController = standardController;
    }

    public PageReference doSomething()
    {
        // Apex code for handling record from a Detail page goes here
        Id recordId = standardController.getId();
        Test__c record = (Test__c) standardController.getRecord();
        return null;
    }
}

Or to have your Apex logic run as soon as the user presses the Custom Button use the action attribute.

<apex:page standardController="Test__c" extensions="DetailButtonController"
           action="{!doSomething}">

To add the Custom Button should look something like this…

Screen Shot 2013-07-16 at 07.43.22

List View Custom Button Template

Example page and class, using apex:commandButton to invoke the logic.

<apex:page standardController="Test__c" extensions="ListButtonController"
           recordSetVar="TestRecords">
    <apex:form >
        <apex:commandButton value="Do something" action="{!doSomething}"/>
    </apex:form>
</apex:page>

Apex controller code.

public with sharing class ListButtonController
{
    private ApexPages.StandardSetController standardSetController;

    public ListButtonController(ApexPages.StandardSetController standardSetController)
    {
        this.standardSetController = standardSetController;
    }

    public PageReference doSomething()
    {
        // Apex code for handling records from a List View goes here
        List<Test__c> listViewRecords =
            (List<Test__c>) standardSetController.getRecords();
        List<Test__c> selectedListViewRecords =
            (List<Test__c>) standardSetController.getSelected();
        Boolean hasMore = standardSetController.getHasNext();
        return null;
    }
}

Or to have your Apex logic run as soon as the user presses the Custom Button use the action attribute.

<apex:page standardController="Test__c" extensions="ListButtonController"
           action="{!doSomething}" recordSetVar="TestRecords">

To add the Custom Button should look something like this…

Screen Shot 2013-07-16 at 07.43.58
WARNING: Use of ‘action’ attribute on apex:page and CSRF Attacks.

If you use the ‘action‘ attribute as per step 4 your Apex code will execute as soon as the Custom Button is pressed. However if your Apex code performs database updates this is considered unsecured as its possible that your code will be open to a CSRF attack. See this excellent topic from Salesforce for more information. If this is your case its better to use the apex:commandButton option and provide a confirmation button to your user before invoking your Apex code.

Since its Summer’13 release, Salesforce have started to add some support to allow us to use the ‘action’ attribute safely, which gives a better user experience since there is no need for a confirmation button. Currently however, the new ‘Require CSRF protection on GET requests‘ checkbox on the Visualforce page is only considered when the page is used to override the standard Delete button on an object. Hopefully support for Custom Button will arrive soon!

Update: 31st July 2013

Here is a great blog on sharing your Apex Controller class between Detail and List pages, Using one page and controller for both a “Detail Page Button” and a “List Button”.

38 thoughts on “How To: Call Apex code from a Custom Button

  1. Thanks for posting this. Just to clarify – if you use the apex:commandButton, it is a two-click process, correct? First you click the custom button (on the List view in your example) which brings up the VF page, then you have to click the “do something” button, right?

    • Yes that is correct, until Salesforce further deploy the CSRF support mentioned at the bottom of my post, this is an unfortunate UX compromise to ensure your code is secure from this type of attack.

  2. Any solution on how to pass parameters and get back results?
    The standard way to invoke Apex from a Custom button using Webservices and Javascript allows that.

    • The platform automatically creates a StandardController/StandardSetController instance which allows you to retrieve the record/s the user clicked the button on, so this information is passed automatically for you when you reference the instance passed into your controller constructor during your action method (bound either to the page load action aka button press or a commandButton on the page). As regards the results of the action method its up to you, it’s Apex, you can redisplay the page with information on it, redirect to another page (state is retained if it also uses the same controller) or redirect back to the standard UI pages (via one of the methods on the standard controller).

    • BTW, do keep in mind, if your building a managed package, that the web service approach does have some architecture and testing side effects you may not want to propagate around your app as you create new custom buttons. Firstly, the web service requirement leans to you effectively exposing a public API to your button logic, since web service classes also have to be global. As you may know global classes also present packaging issues as they cannot be changed. Thus web services mean you exposing what would normally be button logic indirectly as a API, which may not be what you want. Secondly, the JavaScript code you invoke the service with and likely perform pre and post logic around, is not easily testable, you certainly cannot write regression tests around it using Apex. Finally, though I’ve not seen it recently, they have been seen to be quite sluggish in IE for some reason the comms is quite slow. So I’m personally not that fond of them from an architecture perspective, however they do offer one benifit from a UX perspective, you don’t have to use confirmation buttons. Hope this helps clear up the pros and cons. 🙂

  3. I understand why your solution is way better than the one with webservices and js but what I don’t get is how to implement parameters and return values 😉
    I want to allow users of my package to create a custom button that for a given title and description creates a defect in the systems and returns its Id.

    • Ha no worries… 😉 In your use case where do the users place this button and where does the title and description originate from? Do they enter it? Can you explain a bit more about the UI flow of your use case and I’ll map it to how I would do this with Apex/VF custom buttons as best I can. Thanks!

      • Our package should contain a custom button that can be added by users to the Case Layout. For a given case the button creates a custom object called Defect__c.

        Currently the user adds a custom button calls our global service and passes Title and Description. Our service creates and insert a Defect record and returns the Id.

        This Id is then used by customer code to populate and open this record.

      • In this case I would package the Custom Button on Case, leaving the admin who installed the package simply to drag and drop the button the Case layout. If the button is implemented as per my blog, you can obtain the Title and Description Case field values via the StandardController.getRecord method. If your concerned about CSRF you will need a confirmation button before creating your Defect record. Perhaps use it as a means to confirm the Title and Description that will be used, perhaps allowing them to overtype.

  4. I finally tried this out and it is not working as expected.
    I created a Detail Button as described in your example with a page referencing a Controller Extension action via action parameter.
    This action return a null Page Reference as in you code. When I click this button the user is redirected to the empy page instead staying on the Account view page. How can a guarantee that the button click does no redirect at all and just call my Apex method?

    • Can you provide a link to your VF page and controller and I will take a quick look for you.

    • Sorry a Gist link

    • Hello Robert,
      I have similar problem. I have a custom button from which I need to call an apex class without opening new tab or page. How we can achieve this? Please help me.

      Thanks,
      tech.raghu@gmail.com

      • If your button is going to update the database, though your not forced to, you need to present the user with a confirmation button on another page, to avoid CSRF attacks. If you and your users are happy this is low risk, you can indeed simply put an ‘action’ attribute on the VF page and perform the DML in the Apex method, then use a PageReference to return to the native UI view page. If you would like to upvote the CSRF support for Custom Buttons please do so here it would be much appreciated! https://success.salesforce.com/ideaView?id=08730000000hy8yAAA&sort=2

  5. Sorry GitHub was down, so I used PasteBin:

    – Controller Extension: http://pastebin.com/8vdxZt4c
    – Page: http://pastebin.com/asK7seVq
    – Button Configuration as Screenshot: https://dl.dropboxusercontent.com/u/240888/detailpagebutton.png

    • I’ve read your code and question again and its working as I would expect. Returning ‘null’ will leave the user on the Visualforce page linked with the button, this is standard behaviour for Visualforce controller methods. Since you don’t have any markup on your page, if you return null from line 16 on your controller you will see a white page when you press the button. Keeping in mind the CSRF security risk, you should ideally prompt them before making an update. However if your not worried in your case about this, the code you have does appear to do what you want, in that is returns the to the View page of the record. Apologies if I’ve missed something but your code appears to do what your asking? (I’ve not tried it on Account btw, but i have identical code on a custom object).

  6. Very helpful article. I’m trying to use a standardcontroller with a List View in a standalone VF page, not a button. The issue I have is that I want the List View to be sorted by a field in the list view. When I sort the actual List View the getRecords returns the records sorted by the Name, not the field that is sorted. When I sort the query string in the getQueryLocator, the getRecords returned are sorted correctly before I apply the setFilterId. However, when I apply the setFilterID the getRecords returned are sorted by the Name and not the desired field.
    Here are the two getter/setters:
    public ApexPages.StandardSetController setCon
    {get{
    if(setCon == null)
    {
    string queryString = ‘SELECT Name, createdDate FROM AO__c order by createdDate DESC Limit 10000’;
    setCon = new ApexPages.StandardSetController(Database.getQueryLocator(queryString));
    setCon.setPageSize(20);
    listViewOptions = setCon.getListViewOptions();
    setCon.setFilterID(listViewOptions[0].getValue());
    if (filterId != null)
    setCon.setFilterID(filterId);

    }
    return setCon;
    }set;
    }

    public List AOList
    {get{
    AO__c[] AOList = new AO__c[]{};
    for (AO__c ao :(List)setCon.getRecords())
    AOList.add(ao);
    return AOList;
    }set;
    }

    • Hmmm i seem to remember this on StackExchange, not able to check at the mo, try searching that, if not raise it in there and me or someone else will likely answer asap!

  7. I did this morning and below is the link which goes into more detail. Your post came up when I did a search on StandardSetController and found your article helpful. http://boards.developerforce.com/t5/Apex-Code-Development/StandardSetController-with-List-View/m-p/680061

  8. Any idea how to add confirm messages before the action is called. With regular buttons I could use Javascript. But here I see not way to intercept this call of the action.

    • I think what you want in the Apex button case, is to place the apex:pageMessages tag on the page, then implement an action method via the apex:page action attribute. The method you give adds the confirmation message via ApexPages.addMessage method call.

      • Sorry for being so unclear 😉 With confirmation I mean asking the user “Do you really want to proceed (Yes/No)?” before the is called.

      • I see its not just a confirmation message. Well if you can tolerate the page being displayed then you can of course but the Yes and No buttons on the page when it is first shown. However if you want a popup when the user clicks the Custom Button, you will have to resort back to JavaScript for this.

  9. How would low or high will you rate this hack to run a visualforce action via a custom button without a confirmation page and avoiding CSRF issues?

    1. Enable “Require CSRF protection on GET requests” on the visualforce page
    2. Create Custom button
    3. Set the content source to javascript
    4. use the following javascript:

    //create a url with the _CONFIRMATIONTOKEN parameter. Since only the CSRF protection only works with delete links, use a delete action
    var url = ‘{!URLFOR($Action.Opportunity.Delete, Opportunity.Id)}’;

    //replace the url with the page we really want. also, pass the id
    url = url.replace(/^[A-Za-z0-9/.]+\?/,’/apex/customapexpage?id={!Opportunity.Id}&’);

    //finally, go to the page!
    navigateToUrl(url);

    • Thats quite a clever hack, technically it doesn’t look to bad in terms of assumptions it makes, it’s unlikely the URL format returned from URLFOR will change and thus break this in the future. You’ve also managed to retain using a standard controller, which is good. I’m not 100% keen on doing JavaScript though, i’ve seen it introduce some lag in getting to the final page. So would still hesitate to use this in a packaged product. But if needs must and you’ve got an unhappy user experience! Nice work!

  10. Hi, I was wondering if you’ve ever seen the following behavior.

    I have a custom button (actually a few of them), that create a VF page with a custom controller so I can create a mix of Case/Opportunity on one page. I pass a few variables to the controller using the ApexPages.currentPage().getParameters().get(‘varname’) function.

    It works perfectly about 99% of the time. Once in a while though, the variables are simply not passed or have a null value. I cannot recreate the problem, so it’s very difficult to debug.

    Is this a bug in SF? Should I not be passing parameters this way?

    Thanks very much

    • Its difficult to tell without seeing more code samples of what your doing, but typically there is a better way than this yes. Can you create a Gist of a sample of your code to make it easier to discuss further?

  11. Hi Andy!

    Thanks so much for this, it’s really helpful. I’m having some issues writing a test for my code and I was hoping you code point me in the right direction. Basically, I’ve created a custom object that maps contacts to campaigns based on certain corresponding criteria (newsletter type and region) and then a custom VF page called from a button that calls a class to clear contacts that should no longer be in the campaigns and add new contacts that should. The code is working fine but I can’t get the test to work.

    Page:


    The Campaign is being updated. If you are not redirected, there’s an error. Contact your admin.

    Class:
    public without sharing class NewsletterSubscriptionsUpdate {

    private ApexPages.StandardController standardController;
    public NewsletterSubscriptionsUpdate(ApexPages.StandardController standardController){
    this.standardController = standardController;
    }

    public PageReference updateSubscriptions (){

    //pull ID from current page and store ID, Region, Newsletter Type(subscription) and Newsletter Campaign ID from Newsletter Map Object
    Id mapID = standardController.getId();
    Newsletter_Subscription_Map__c newsletterMap = [SELECT ID, Region__c, Newsletter_Type__c, Campaign__r.ID FROM Newsletter_Subscription_Map__c WHERE ID = :mapID];

    …a bunch of Queries and DMLs that clear and add the appropriate campaign members….

    PageReference pageRef = new PageReference(‘/’ + mapId);
    pageRef.setRedirect(true);
    return pageRef;
    }
    }

    Test Class:
    private class NewsletterSubscriptionsUpdateTest {

    static testMethod void testUpdate() {

    …a bunch DML to add a contact, a campaign (called testCam1) and a Newsletter Map object (called testMap1) to connect the two…

    ApexPages.StandardController sc = new ApexPages.StandardController(testMap1);
    NewsletterSubscriptionsUpdate controller = new NewsletterSubscriptionsUpdate (sc);
    controller.updateSubscriptions();

    system.assert(testcam1.Number_of_Members__c != null);
    }
    }

    That system assert keeps failing. Any advice would be great!

    • Hard to tell when the code is pasted this way, but at a first guess wondering if the testcam1 instance is stale, have your tried requerying it before the assert?

      • Good call! I re-queried the campaign from the Database and ran the assert against it and it worked fine. Thank you! Now I should be able to stop pestering blogs across the internet and finish this thing.

  12. Whoops, forgot to pull the brackets from the Page code

    apex:page standardController=”Newsletter_Subscription_Map__c”
    extensions=”NewsletterSubscriptionsUpdate”
    action=”{!updateSubscriptions}”

    The Campaign is being updated. If you are not redirected, there’s an error. Contact your admin.

    /apex:page

  13. Pingback: Introduction to the Platform Action API | Andy in the Cloud

  14. Pingback: Winter’17: Using a Lightning Component from an Action | Andy in the Cloud

Leave a comment