Skip to main content

How to Upload and Insert CSV Data into Salesforce Using Visualforce and Apex

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.

In plain words: You can create a file upload interface using Visualforce, capture the file content as a binary blob, and use an Apex controller to parse the text data line-by-line. The code converts those text rows into Salesforce objects (like Contact records) and inserts them into the database in bulk.
Salesforce Custom CSV File Uploader Interface

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.

Real-Life Example: CSV Parsing and Bulk Insertion
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

Watch out for these common traps:
  • 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)

Q: Can I use this code in a modern Lightning Web Component (LWC)?

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.

Q: How do I handle duplicate records during the CSV import?

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.

Core Takeaway: Always validate file payloads, handle exceptions gracefully, and execute bulk DML statements outside iteration loops to maintain a robust data import pipeline.
360 Summary Card
  • Frontend Component: <apex:inputFile>
  • Target Object: Contact (FirstName, LastName, Email)
  • Parsing Strategy: String conversion + row splitting
  • DML Execution: Single bulkified insert statement