Guides Visualforce page creation, controllers, extensions, ViewState debugging, JS Remoting, PDF generation, and VF-to-LWC migration planning in Salesforce.
npx claudepluginhub jiten-singh-shahi/salesforce-claude-code --plugin salesforce-claude-codeThis skill uses the workspace's default tool permissions.
Visualforce is Salesforce's server-side rendering framework. While LWC is the modern standard, Visualforce remains heavily used for PDF generation, email templates, custom overrides, and legacy applications.
Enforces quality standards for Salesforce LWC, Aura, and Visualforce components: SLDS 2 compliance, WCAG 2.1 AA accessibility, data access patterns, security (XSS/CSRF/FLS/CRUD), and Jest tests. Use when building or reviewing UI components.
Provides patterns for Salesforce platform development: Lightning Web Components (LWC), Apex triggers/classes, REST/Bulk APIs, Connected Apps, Salesforce DX with scratch orgs and 2GP.
Writes and debugs Apex code, builds Lightning Web Components, optimizes SOQL queries, implements triggers, batch jobs, platform events, and Salesforce integrations. Use for CRM workflows, governor limits, bulk processing, and Salesforce DX CI/CD.
Share bugs, ideas, or general feedback.
Visualforce is Salesforce's server-side rendering framework. While LWC is the modern standard, Visualforce remains heavily used for PDF generation, email templates, custom overrides, and legacy applications.
renderAs="pdf") — LWC cannot do this@../_reference/VISUALFORCE_PATTERNS.md
| Type | When to Use |
|---|---|
| Standard Controller | Single-record CRUD without custom logic |
| Standard List Controller | List views with built-in pagination |
| Custom Controller | Full control over logic, data, navigation |
| Controller Extension | Add functionality to standard/custom controllers |
<apex:page standardController="Account"
extensions="AccountOverviewExtension"
lightningStylesheets="true"
docType="html-5.0"
title="Account Overview">
<apex:pageBlock title="Account Details">
<apex:pageBlockSection columns="2">
<apex:outputField value="{!Account.Name}" />
<apex:outputField value="{!Account.Industry}" />
</apex:pageBlockSection>
</apex:pageBlock>
</apex:page>
public with sharing class InvoiceController {
public List<Invoice__c> invoices { get; private set; }
public String searchTerm { get; set; }
public InvoiceController() {
searchTerm = '';
loadInvoices();
}
public PageReference search() {
loadInvoices();
return null; // Stay on same page
}
private void loadInvoices() {
String likeSearch = '%' + String.escapeSingleQuotes(searchTerm) + '%';
invoices = [
SELECT Id, Name, Amount__c, Status__c, CreatedDate
FROM Invoice__c WHERE Name LIKE :likeSearch
WITH USER_MODE ORDER BY CreatedDate DESC LIMIT 100
];
}
}
public with sharing class AccountOverviewExtension {
private final Account account;
// Required constructor signature
public AccountOverviewExtension(ApexPages.StandardController stdController) {
if (!Test.isRunningTest()) {
stdController.addFields(new List<String>{ 'OwnerId', 'AnnualRevenue' });
}
this.account = (Account) stdController.getRecord();
}
public List<Contact> relatedContacts {
get {
if (relatedContacts == null) {
relatedContacts = [
SELECT Id, Name, Email, Phone
FROM Contact WHERE AccountId = :account.Id
WITH USER_MODE ORDER BY Name LIMIT 50
];
}
return relatedContacts;
}
private set;
}
}
ViewState is a hidden, encrypted form field that maintains page state across postbacks. 170KB limit — exceeding it causes a runtime error.
transient KeywordMark variables that do not need to survive postbacks as transient:
public with sharing class ReportController {
// IN ViewState — needed across postbacks
public String selectedFilter { get; set; }
public Integer currentPage { get; set; }
// NOT in ViewState — recomputed on each request
transient public List<AggregateResult> reportData { get; private set; }
transient public Blob chartImage { get; private set; }
}
| Strategy | Impact |
|---|---|
transient keyword on large/recomputable variables | High |
apex:outputPanel + reRender (partial refresh) | Medium |
| Paginate large data sets | High |
| Use JavaScript Remoting (stateless) | High |
Move read-only data outside apex:form | Medium |
Stateless, high-performance Apex calls that bypass ViewState entirely.
@RemoteAction
public static List<Account> findAccounts(String searchTerm) {
String safeTerm = '%' + String.escapeSingleQuotes(searchTerm) + '%';
return [
SELECT Id, Name, Industry FROM Account
WHERE Name LIKE :safeTerm WITH USER_MODE LIMIT 25
];
}
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.AccountSearchController.findAccounts}',
term,
function(result, event) {
if (event.status) {
renderResults(result);
} else {
console.error(event.message);
}
},
{ escape: true, timeout: 30000 }
);
Use {!$RemoteAction.ClassName.methodName} (namespace-safe). Set escape: true to prevent XSS.
<apex:actionFunction name="refreshDashboard" action="{!refresh}"
reRender="dashPanel" status="loadingStatus" />
<apex:selectList value="{!selectedRegion}" size="1">
<apex:selectOptions value="{!regionOptions}" />
<apex:actionSupport event="onchange" action="{!filterByRegion}"
reRender="dashPanel" status="loadingStatus" />
</apex:selectList>
<apex:actionStatus id="loadingStatus">
<apex:facet name="start"><img src="/img/loading.gif" alt="Loading..." /></apex:facet>
</apex:actionStatus>
| Keep Visualforce | Migrate to LWC |
|---|---|
PDF generation (renderAs="pdf") | High-traffic pages needing performance |
| Email templates | New feature development |
| Complex server-state wizards | Pages using Apex controller only |
| Visualforce | LWC |
|---|---|
apex:pageBlockTable | lightning-datatable |
apex:commandButton action="{!save}" | lightning-button onclick={handleSave} + imperative Apex |
apex:inputField | lightning-input-field (in lightning-record-edit-form) |
| JavaScript Remoting | @wire or imperative Apex import |
apex:actionSupport | Standard DOM event handlers |
{!property} merge fields | {property} template expressions |
For incremental migration, embed LWC inside existing VF pages:
<apex:includeLightning />
<div id="lwc-container"></div>
<script>
$Lightning.use("c:lwcOutApp", function() {
$Lightning.createComponent("c:accountDashboard",
{ recordId: "{!Account.Id}" }, "lwc-container");
});
</script>