Skip to main content

CSV Upload and Record Creation in Salesforce LWC: Step-by-Step Guide

In plain words: A CSV Uploader Component in LWC allows users to import spreadsheet data directly into Salesforce without navigating through standard data import wizards. The component accepts a .csv file, uploads it via Salesforce Files, and passes the generated document ID to an Apex controller that parses rows and commits new records in bulk.

Importing data quickly from external systems is a standard business requirement. While administrators have access to tools like the Data Import Wizard and Data Loader, business users often need a simple, self-service interface on their home page or record view to import structured spreadsheets. Building a custom CSV import utility in Lightning Web Components gives teams immediate data creation capabilities wrapped in custom validation logic.

Uploading CSV and Creating Records in Salesforce LWC

1. Solution Architecture: Frontend Upload to Backend Processing

Processing user-uploaded CSV files requires three coordinated layers:

  • Client Layer (<lightning-file-upload>): Captures the file directly from the browser, streams it securely to Salesforce Files, and receives a unique documentId upon completion.
  • Bridge Layer (JavaScript Controller): Handles the onuploadfinished event, extracts the document token, and invokes an imperative Apex method.
  • Server Layer (Apex Controller): Queries the underlying ContentVersion binary data, converts the blob to text, parses line items, and commits records using bulk DML in User Mode.
360 CSV Processing Architecture Card:
  • UI Upload Component: <lightning-file-upload accept=".csv">
  • Backend Data Model: ContentVersion & ContentDocument
  • DML Strategy: Bulkified collection inserts with User Mode enforcement (insert as user).
  • Notification Service: Platform ShowToastEvent for real-time success and failure alerts.

2. Step-by-Step Implementation

Step 1: Build the Template Markup (csvUploader.html)
Use the standard file upload base component configured exclusively for CSV MIME types.
<template>
    <lightning-card title="CSV Record Importer" icon-name="custom:custom18">
        <div class="slds-p-around_medium">
            <lightning-file-upload
                label="Select CSV File"
                name="csvFileUploader"
                accept=".csv"
                multiple="false"
                onuploadfinished={handleUploadFinished}>
            </lightning-file-upload>
        </div>
    </lightning-card>
</template>
Step 2: Implement the JavaScript Controller (csvUploader.js)
Capture the uploaded file ID and trigger the Apex processor asynchronously.
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import processCSV from '@salesforce/apex/CSVProcessor.processCSV';

export default class CsvUploader extends LightningElement {

    async handleUploadFinished(event) {
        const uploadedFiles = event.detail.files;
        
        if (uploadedFiles && uploadedFiles.length > 0) {
            const uploadedDocumentId = uploadedFiles[0].documentId;
            
            try {
                const totalInserted = await processCSV({ fileId: uploadedDocumentId });
                
                this.showToast(
                    'Success',
                    `${totalInserted} records created successfully from CSV.`,
                    'success'
                );
            } catch (error) {
                const errorMsg = error.body ? error.body.message : error.message;
                this.showToast('Import Failed', errorMsg, 'error');
            }
        }
    }

    showToast(title, message, variant) {
        this.dispatchEvent(
            new ShowToastEvent({
                title: title,
                message: message,
                variant: variant
            })
        );
    }
}
Step 3: Implement the Apex Processing Controller (CSVProcessor.cls)
Fetch the uploaded blob, parse rows, and insert records defensively in User Mode.
public with sharing class CSVProcessor {

    @AuraEnabled
    public static Integer processCSV(Id fileId) {
        if (fileId == null) {
            throw new AuraHandledException('No valid File ID provided.');
        }

        // Query the latest ContentVersion record associated with the ContentDocument
        ContentVersion fileData = [
            SELECT Id, VersionData, FileExtension 
            FROM ContentVersion 
            WHERE ContentDocumentId = :fileId 
            WITH USER_MODE 
            ORDER BY CreatedDate DESC 
            LIMIT 1
        ];

        if (fileData.VersionData == null) {
            throw new AuraHandledException('Uploaded file contains no data.');
        }

        String fileContent = fileData.VersionData.toString();
        List<String> lines = fileContent.split('\n');

        if (lines.size() <= 1) {
            throw new AuraHandledException('CSV file must contain a header and at least one row of data.');
        }

        List<Account> accountsToInsert = new List<Account>();

        // Loop through data rows, skipping the header index 0
        for (Integer i = 1; i < lines.size(); i++) {
            String row = lines[i].trim();
            if (String.isNotBlank(row)) {
                List<String> columns = row.split(',');

                // Example mapping: Column 0 = Name, Column 1 = Industry, Column 2 = Phone
                if (columns.size() >= 2) {
                    Account acc = new Account();
                    acc.Name = columns[0].trim();
                    acc.Industry = columns[1].trim();
                    if (columns.size() >= 3) {
                        acc.Phone = columns[2].trim();
                    }
                    accountsToInsert.add(acc);
                }
            }
        }

        if (!accountsToInsert.isEmpty()) {
            insert as user accountsToInsert;
        }

        return accountsToInsert.size();
    }
}

3. Common Traps & Platform Best Practices

Heap Size & Parsing Trap: Naive String Splitting on Large Files
Converting large files via VersionData.toString() and running .split('\n') stores multiple duplicates of the entire file in memory simultaneously. If the CSV exceeds 4–5 MB, the transaction will crash with an uncatchable LimitException: Apex heap size too large. For large enterprise files, use Batch Apex or parse CSVs on the client side using JavaScript libraries before chunking payloads to Apex.
Core Rule: Validate CSV header formats before processing, sanitize column inputs against commas and quotes, and execute bulk inserts using "insert as user" to enforce object and field-level security.
  • Handling Commas Inside Quotes: Basic .split(',') breaks when cell values contain internal commas (e.g., "Acme, Inc."). For complex datasets, use a robust regex or dedicated CSV parser class.
  • Enforce Partial Success Options: If some rows might fail validation rules, consider using Database.insert(records, false) to allow valid rows to commit while collecting and reporting errors for failed rows.
  • Clean Up Staging Files: If the uploaded CSV is only needed for temporary record creation, consider deleting the staging ContentDocument after processing to preserve Salesforce file storage limits.

Summary

Building a CSV file upload component in Lightning Web Components delivers an intuitive, self-service data import experience for Salesforce users. By combining <lightning-file-upload> with structured, bulkified Apex processing and User Mode security, developers can automate record creation safely while maintaining enterprise performance standards.