Importing data in bulk from a local spreadsheet is a standard requirement for many enterprise applications. While tools like Data Loader handle this out of the box, developers often need to build custom, self-service tools that allow end-users to upload and process CSV files directly from a custom page layout.
Contact records) and inserts them into the database in bulk.
Key Points Summary
- The
<apex:inputFile>component lets users choose local CSV files from their device. - Apex reads the file contents by converting the binary data into a string using the
toString()method. - Rows are split using line break delimiters (
\n), and the header row is skipped safely using a counter loop index. - All records are compiled into a list and inserted using a single bulkified DML operation to respect governor limits.
1. The Visualforce Frontend Page
The Visualforce page provides a simple file picker interface. Once a file is selected and uploaded, a table below renders a live preview of the successfully processed records.
<apex:page controller="ReadAndInsertController">
<apex:form>
<apex:pageMessages />
<apex:pageBlock title="Upload CSV File">
<apex:actionRegion>
<apex:inputFile value="{!uploadedFileContent}" filename="{!fileName}" />
<apex:commandButton action="{!uploadRecords}" value="Upload File" styleClass="slds-button slds-button_brand" />
</apex:actionRegion>
<apex:pageBlockTable value="{!uploadedContacts}" id="conTable" var="con" style="margin-top:15px;">
<apex:column headerValue="First Name">
<apex:outputText value="{!con.FirstName}" />
</apex:column>
<apex:column headerValue="Last Name">
<apex:outputText value="{!con.LastName}" />
</apex:column>
<apex:column headerValue="Email">
<apex:outputText value="{!con.Email}" />
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
2. The Apex Controller Logic
The Apex controller receives the file blob, converts it to plain text, splits it by row breaks, and maps each column value to a new Contact sObject instance.
This controller maps a standard three-column layout (FirstName, LastName, Email) into Salesforce records:
public with sharing class ReadAndInsertController {
public String fileName { get; set; }
public Blob uploadedFileContent { get; set; }
public List<Contact> contactsToUpload { get; set; }
public PageReference uploadRecords() {
if (uploadedFileContent == null) {
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Please select a CSV file first.'));
return null;
}
try {
String fileString = uploadedFileContent.toString();
List<String> fileRows = fileString.split('\n');
contactsToUpload = new List<Contact>();
// Skip index 0 assuming it contains CSV header columns
for (Integer i = 1; i < fileRows.size(); i++) {
String row = fileRows[i].trim();
if (String.isNotBlank(row)) {
String[] columnValues = row.split(',');
Contact con = new Contact();
con.FirstName = columnValues.size() > 0 ? columnValues[0].trim() : '';
con.LastName = columnValues.size() > 1 ? columnValues[1].trim() : '';
con.Email = columnValues.size() > 2 ? columnValues[2].trim() : '';
contactsToUpload.add(con);
}
}
if (!contactsToUpload.isEmpty()) {
insert contactsToUpload;
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.CONFIRM, 'Records inserted successfully!'));
}
} catch (Exception e) {
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Error processing CSV file: ' + e.getMessage()));
}
return null;
}
public List<Contact> getUploadedContacts() {
if (contactsToUpload != null && !contactsToUpload.isEmpty()) {
return contactsToUpload;
}
return null;
}
}
Common Developer Pitfalls & Limitations
- Heap Size Limitations: Synchronous Apex heap limits (~6 MB) will crash your transaction if users upload massive CSV files containing thousands of rows. For large datasets, utilize LWC with client-side chunking or standard data tools.
- Commas Inside Quoted Values: A simple
row.split(',')will break if a data field contains a comma wrapped in quotation marks (e.g.,"Smith, Jr."). Use regex-based parsing if your data contains quoted fields. - Missing Governor Limit Protections: Always perform DML operations outside loops to ensure you never exceed Salesforce governor limits.
Frequently Asked Questions (FAQ)
Yes. In modern LWC architectures, you can use the HTML5 FileReader API via JavaScript to read the CSV client-side, convert it into a JSON string or list, and pass it imperatively to an Apex controller for safe insertion.
You can use Database methods like Database.insert(list, false) to allow partial success, or cross-reference incoming email addresses against existing database records before executing the insert statement.
- Frontend Component:
<apex:inputFile> - Target Object: Contact (
FirstName,LastName,Email) - Parsing Strategy: String conversion + row splitting
- DML Execution: Single bulkified insert statement