A custom controller in Salesforce is an Apex class that supplies all controller logic for a Visualforce page. Unlike a Standard controllers, a custom controller does not automatically provide the built-in save, edit, delete, cancel, list, or record navigation behavior for an object. You write the properties and action methods yourself, then bind them to Visualforce pages with merge expressions such as {!account.Name} and {!save}.
Use a custom controller when the page needs logic that is not a simple record detail page: custom queries, multiple objects, conditional buttons, calculated values, wizard-style navigation, or business rules that are easier to handle in Apex. If you only need to add a small amount of logic to standard record behavior, a controller extension is often the better choice because it keeps the standard controller features and adds custom Apex on top.
When Custom Controller in Salesforce is used?
- Custom Controller in Salesforce is used when a Visualforce page needs logic beyond the built-in functionality of a standard controller.
- It is used to add new actions, custom buttons, custom navigation, search filters, or business rules.
- It is useful when the page works with multiple records or multiple objects instead of one standard record page.
- It can be used when the Visualforce page must decide exactly which records are queried and how those records are displayed or updated.
- Custom controller and controller extension classes execute in system context, so security must be handled carefully.
- User record access can be respected by declaring the Apex class with the with sharing keyword. This applies sharing rules such as Organization wide defaults, Role hierarchy, and Sharing rules.
- Object permissions and field level security are not automatically enforced by custom Apex in the same way as standard Visualforce behavior, so production controllers should check CRUD/FLS explicitly before reading or updating data.
Custom controller, standard controller, and controller extension difference
| Visualforce controller type | How it is declared on the page | Best use case | Important note |
|---|---|---|---|
| Standard controller | standardController=”Account” | Record pages that need built-in Salesforce actions such as save, edit, delete, and cancel. | Works with one standard or custom object and gives many record operations without writing Apex. |
| Custom controller | controller=”CustomController” | Pages that need fully custom Apex logic, custom queries, or data from more than one object. | You must write the getter methods, action methods, queries, DML, navigation, and security checks. |
| Controller extension | standardController=”Account” extensions=”AccountExtension” | Pages that need standard controller behavior plus a few custom methods. | The extension receives the standard controller instance in its constructor. |
How to build Custom Controller in Salesforce?
Custom Controller in Salesforce is an Apex class. In Lightning Experience, open Setup, search for Apex Classes, and click New. In Salesforce Classic, the older navigation path is Setup | Build | Develop | Apex Classes | New. You can also create the Apex class from the Developer Console using File | New | Apex Class.

- Click on Version settings before creating Custom controller in Salesforce. The version settings specify the Apex version and API version used by the class.
- Now click on Apex class editor to write Apex class for Custom controller in Salesforce.
- Name the Apex class clearly. In this tutorial, the controller class name is CustomController.
- Save the class before using it in the controller attribute of a Visualforce page.
public class CustomController {
private final Account account;
public CustomController() {
account = [SELECT Id, Name, Site FROM Account
WHERE Id = :ApexPages.currentPage().getParameters().get('id')];
}
public Account getAccount() {
return account;
}
public PageReference save() {
update account;
return null;
}
}
This Apex class has three important parts:
- private final Account account; stores the Account record used by the page.
- CustomController() is the constructor. It runs when the Visualforce page is opened and reads the id parameter from the page URL.
- getAccount() is a getter method. Visualforce can reference it as {!account}.
- save() is an action method. The Visualforce command button can call it with action=”{!save}”.
The example is intentionally simple for learning. In a production controller, validate that the id parameter is present, handle missing records, use with sharing when user record access must be respected, and check object and field permissions before DML.

- CustomController is the name of the Apex controller class used by the Visualforce page.
- Click on Save button.

Visualforce syntax for connecting a page to a custom controller
The custom controller is connected to the Visualforce page by the controller attribute on the <apex:page> tag. The value must match the Apex class name.
<apex:page controller="ApexClassName">
...
</apex:page>
For the class created above, the Visualforce page uses controller=”CustomController”.
How to implement Custom controller in Visualforce pages.
To implement Custom Controller in Visualforce pages, first create a new Visualforce page in Salesforce. Follow the steps given below to implement custom controller logic on the page.
- Click on the link to create new Visualforce page in salesforce.

- In this Visualforce page, the custom controller opens one Account record in a form. The Account id must be added at the end of the URL.
- https://tutorialkart-dev-ed–tutorialkart.ap4.visual.force.com/apex/customcontroller?id=0016F000026m5ZB
<apex:page controller="CustomController" tabStyle="Account">
<apex:form>
<apex:pageBlock title="Congratulations {!$User.FirstName}">
You belong to Account Name: <apex:inputField value="{!account.name}"/>
<apex:commandButton action="{!save}" value="save"/>
</apex:pageBlock>
</apex:form>
</apex:page>

The Custom controller is associated with the page because of the controller attribute of the <apex:page> component. The custom controller method in Salesforce must be referenced with the Visualforce expression syntax {!…}.
- {!$User.FirstName} retrieves the first name of the current Salesforce user from the global user variable.
- {!account.name} calls the getAccount() method and binds the Account Name field to the input field.
- {!save} calls the save() action method when the command button is clicked.
In the Apex class above, the getAccount() method is referenced by the <apex:inputField> tag in the Visualforce page, and the <apex:commandButton> tag references the save() method with its action attribute.
- Finally click on Save.
In the output section, if we rename the Account Name and click save, the Account record is updated with the new name.
Result: The Account record is updated, and the Visualforce page remains on the same page because the save method returns null.
Safer custom controller pattern for Visualforce Account updates
The learning example above shows the basic structure. For real Salesforce orgs, the controller should be more defensive. The following pattern shows common safeguards: with sharing, an id check, a limited query, and permission handling before update.
public with sharing class AccountEditController {
public Account account { get; private set; }
public AccountEditController() {
String accountId = ApexPages.currentPage().getParameters().get('id');
if (String.isBlank(accountId)) {
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.ERROR,
'Account id is required.'
));
account = new Account();
return;
}
List<Account> records = [
SELECT Id, Name, Site
FROM Account
WHERE Id = :accountId
LIMIT 1
];
if (records.isEmpty()) {
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.ERROR,
'Account was not found.'
));
account = new Account();
return;
}
account = records[0];
}
public PageReference save() {
if (!Schema.sObjectType.Account.isUpdateable()) {
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.ERROR,
'You do not have permission to update Account records.'
));
return null;
}
SObjectAccessDecision decision =
Security.stripInaccessible(AccessType.UPDATABLE, new List<Account>{ account });
update decision.getRecords();
ApexPages.addMessage(new ApexPages.Message(
ApexPages.Severity.CONFIRM,
'Account updated successfully.'
));
return null;
}
}
This example still needs to be adapted to your org. For example, if your page displays or updates additional fields, add only the fields you need and verify access for those fields. Custom controllers are Apex code, so the developer is responsible for avoiding unnecessary queries, handling exceptions, and writing tests.
Apex test class for the CustomController Visualforce example
Salesforce requires Apex tests for deployment to production. A Visualforce custom controller test usually creates test data, sets the current page, adds URL parameters, constructs the controller, calls its methods, and verifies the database result.
@IsTest
private class CustomControllerTest {
@IsTest
static void saveUpdatesAccountName() {
Account testAccount = new Account(Name = 'Original Account');
insert testAccount;
Test.setCurrentPage(new PageReference('/apex/customcontroller'));
ApexPages.currentPage().getParameters().put('id', testAccount.Id);
CustomController controller = new CustomController();
Account pageAccount = controller.getAccount();
pageAccount.Name = 'Updated Account';
Test.startTest();
PageReference result = controller.save();
Test.stopTest();
Account savedAccount = [
SELECT Name
FROM Account
WHERE Id = :testAccount.Id
];
System.assertEquals(null, result, 'The save method stays on the same page.');
System.assertEquals('Updated Account', savedAccount.Name);
}
}
If your org has required custom fields or validation rules on Account, add those required values to the test record. Test data created in a test method is isolated from real organization data.
Common errors in Salesforce custom controller pages
- Unknown controller class: The value in controller=”…” does not match an existing Apex class name, or the class was not saved successfully.
- List has no rows for assignment to SObject: The SOQL query expects one record, but the URL does not contain a valid record id or the record is not found.
- Insufficient access or missing field access: The controller updates fields without checking object permissions, field permissions, or sharing needs.
- Action method not found: The method referenced by action=”{!methodName}” is missing, private, misspelled, or has the wrong return type for the intended action.
- Input field does not display: The Visualforce expression does not resolve to an sObject field, or the field is not included in the controller property used by the page.
Editorial QA checklist for this Salesforce custom controller tutorial
- Confirm that every Visualforce page using this tutorial has controller=”CustomController” only when the Apex class name is exactly CustomController.
- Check that the Account record id is passed in the page URL before testing the example page.
- Verify that any production controller uses with sharing when record-level sharing should apply.
- Review CRUD and field-level security checks before showing or updating fields from custom Apex.
- Run the Apex test class in a sandbox or developer org and add required custom fields if the local Account object has validation rules.
Official Salesforce references for Visualforce custom controllers
For deeper reference, use Salesforce’s own documentation on custom controllers, standard controllers and extensions, and the Trailhead unit on Visualforce custom controllers. These references are helpful when you need exact syntax, controller behavior, and official examples.
FAQs on custom controller in Salesforce
How do I create a custom controller in Salesforce?
Create an Apex class, make it public, add any getter methods and action methods needed by the Visualforce page, and save it. Then use the class name in the Visualforce page with <apex:page controller=”YourClassName”>.
What is the difference between a custom controller and a controller extension?
A custom controller replaces the standard controller completely, so you write the page behavior yourself. A controller extension adds extra Apex logic to a standard controller, so the page can still use built-in record behavior such as save and cancel.
Does a Visualforce custom controller respect Salesforce sharing rules?
A custom controller can respect sharing rules when the Apex class is declared with sharing. This handles record-level sharing. Object permissions and field-level security still need explicit handling in custom Apex.
Can a custom controller work without a record id in the URL?
Yes, but only if the controller logic does not require a specific record. The Account edit example in this tutorial expects an id parameter because it queries one Account record from the URL.
What return type should a custom controller action method use?
Visualforce action methods commonly return PageReference. Returning null keeps the user on the same page, while returning a PageReference can redirect or navigate to another page.
TutorialKart.com