skills/sf-visualforce-development/SKILL.md
Visualforce development — pages, controllers, extensions, ViewState, JS Remoting, LWC migration. Use when maintaining VF pages, building PDFs, or planning VF-to-LWC migration. Do NOT use for LWC, Aura, or Flow.
npx skillsauth add jiten-singh-shahi/salesforce-claude-code sf-visualforce-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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>
development
Update Salesforce platform reference docs with latest release features and deprecation announcements. Use when SessionStart hook warns docs are outdated or a new Salesforce release has shipped. Do NOT use for Apex or LWC development.
development
Use when syncing documentation after Salesforce Apex code changes. Update README, API docs, and deploy metadata references to match the current org codebase.
development
Use when managing context during long Salesforce Apex development sessions. Suggests manual compaction at logical intervals to preserve deploy and org context across phases.
development
Salesforce Apex trigger framework patterns — TriggerHandler, FFLIB Domain, TDTM, bypass and recursion control. Use when adopting or refactoring triggers.