Documentation Version
Winter '16 (API version 35.0)
  • Winter '16 (API version 35.0) 35.0
  • Summer '15 (API version 34.0) 34.0
  • Spring '15 (API version 33.0) 33.0
  • Winter '15 (API version 32.0) 32.0
  • Summer '14 (API version 31.0) 31.0
  • Spring '14 (API version 30.0) 30.0
  • Winter '14 (API version 29.0) 29.0
  • English

Building a Custom List Controller

A custom list controller is similar to a standard list controller. Custom list controllers can implement Apex logic that you define to show or act on a set of records.

For example you can create the following custom list controller based on a SOQL query:
public class opportunityList2Con {
    // ApexPages.StandardSetController must be instantiated
    // for standard list controllers
    public ApexPages.StandardSetController setCon {
        get {
            if(setCon == null) {
                setCon = new ApexPages.StandardSetController(Database.getQueryLocator(
                    [SELECT Name, CloseDate FROM Opportunity]));
            return setCon;

    // Initialize setCon and return a list of records
    public List<Opportunity> getOpportunities() {
        return (List<Opportunity>) setCon.getRecords();


The list of sObjects returned by getRecords() is immutable. For example, you can’t call clear() on it. You can make changes to the sObjects contained in the list, but you can’t add items to or remove items from the list itself.

The following Visualforce markup shows how the custom controller above can be used in a page:
<apex:page controller="opportunityList2Con">
        <apex:pageBlockTable value="{!opportunities}" var="o">
            <apex:column value="{!o.Name}"/>
            <apex:column value="{!o.CloseDate}"/>
You can also create a custom list controller that uses anti- and semi-joins as part of the SOQL query. The following code is implemented as an extension to the account standard controller:
public with sharing class AccountPagination {
    private final Account acct;  

    // The constructor passes in the standard controller defined
    // in the markup below
    public AccountPagination(ApexPages.StandardSetController controller) {
        this.acct = (Account)controller.getRecord(); 
    public ApexPages.StandardSetController accountRecords {
        get {
            if(accountRecords == null) {
                accountRecords = new ApexPages.StandardSetController(
                    Database.getQueryLocator([SELECT Name FROM Account WHERE Id NOT IN 
                        (SELECT AccountId FROM Opportunity WHERE IsClosed = true)]));
            return accountRecords;
        private set;
    public List<Account> getAccountPagination() {
         return (List<Account>) accountRecords.getRecords();
The page that displays these records uses a mix of standard list controller actions, but depends on iterating over the records returned from the custom list controller:
<apex:page standardController="Account" recordSetVar="accounts" extensions="AccountPagination">
    <apex:pageBlock title="Viewing Accounts">
        <apex:form id="theForm">
            <apex:pageBlockSection >
                <apex:dataList value="{!accountPagination}" var="acct" type="1">
            <apex:panelGrid columns="2">
                <apex:commandLink action="{!previous}">Previous</apex:commandlink>
                <apex:commandLink action="{!next}">Next</apex:commandlink>