.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.
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 uniquedocumentIdupon completion. - Bridge Layer (JavaScript Controller): Handles the
onuploadfinishedevent, extracts the document token, and invokes an imperative Apex method. - Server Layer (Apex Controller): Queries the underlying
ContentVersionbinary data, converts the blob to text, parses line items, and commits records using bulk DML in User Mode.
- 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
ShowToastEventfor real-time success and failure alerts.
2. Step-by-Step Implementation
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>
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
})
);
}
}
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
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.
- 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
ContentDocumentafter 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.