Exporting Salesforce records to a standard .csv file is one of the most requested features across administrative dashboards and custom list views. Whether you are maintaining existing Aura implementations or integrating custom export workflows, combining an @AuraEnabled Apex query with client-side JavaScript file generation delivers a clean, responsive user experience.
Prerequisites
- A Salesforce Developer Edition or Sandbox environment.
- Basic understanding of Lightning Aura Component architecture (Component, Controller, Helper) and Apex controllers.
Step 1: Create the Server-Side Apex Controller
First, write an Apex controller that queries the records you want to export. Mark the method with @AuraEnabled(cacheable=true) for optimal performance and read access.
/* ExportDataToCSVController.cls */
public with sharing class ExportDataToCSVController {
@AuraEnabled(cacheable=true)
public static List<Contact> retrieveData() {
return [
SELECT Id, FirstName, LastName, Email, Title, Department
FROM Contact
WITH USER_MODE
LIMIT 1000
];
}
}
Explanation: The WITH USER_MODE clause ensures that the query adheres to the running user's Object and Field-Level Security (FLS) permissions.
Step 2: Build the Aura Component Markup
Create the component definition file (.cmp) connecting your Apex class via the controller attribute and adding a trigger button.
<!-- ExportDataToCSV.cmp -->
<aura:component controller="ExportDataToCSVController" implements="flexipage:availableForAllPageTypes">
<lightning:card title="Data Export" iconName="utility:download">
<div class="slds-p-around_medium">
<p class="slds-m-bottom_small">Download contact records in CSV format:</p>
<lightning:button
variant="brand"
label="Export to CSV"
iconName="utility:download"
onclick="{! c.exportToCSV }" />
</div>
</lightning:card>
</aura:component>
Step 3: Implement the Client-Side Controller
The controller handles the button click event, enqueues the server-side action, and hands the retrieved record array over to the helper.
/* ExportDataToCSVController.js */
({
exportToCSV: function(component, event, helper) {
var action = component.get("c.retrieveData");
action.setCallback(this, function(response) {
var state = response.getState();
if (state === "SUCCESS") {
var records = response.getReturnValue();
if (records && records.length > 0) {
var csvContent = helper.convertArrayOfObjectsToCSV(records);
helper.triggerDownload(csvContent, "Contact_Export.csv");
} else {
console.log("No records returned to export.");
}
} else if (state === "ERROR") {
var errors = response.getError();
console.error("Error retrieving records:", errors);
}
});
$A.enqueueAction(action);
}
})
Step 4: Implement the Helper Functions
The helper manages data transformation, handles comma and quotation character escaping, and executes the file download using the modern Blob and URL.createObjectURL APIs.
/* ExportDataToCSVHelper.js */
({
convertArrayOfObjectsToCSV: function(data) {
if (!data || !data.length) {
return null;
}
var columnDivider = ',';
var lineDivider = '\r\n';
// Extract column keys from the first record (excluding Aura metadata properties)
var keys = Object.keys(data[0]).filter(function(key) {
return key !== 'attributes';
});
var csvStringResult = '';
csvStringResult += keys.join(columnDivider);
csvStringResult += lineDivider;
for (var i = 0; i < data.length; i++) {
var rowValues = [];
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
var cellValue = data[i][key] !== undefined && data[i][key] !== null ? String(data[i][key]) : '';
// Escape internal quotes and enclose fields containing delimiters
if (cellValue.includes(',') || cellValue.includes('"') || cellValue.includes('\n')) {
cellValue = '"' + cellValue.replace(/"/g, '""') + '"';
}
rowValues.push(cellValue);
}
csvStringResult += rowValues.join(columnDivider);
csvStringResult += lineDivider;
}
return csvStringResult;
},
triggerDownload: function(csvData, fileName) {
// Prepend UTF-8 BOM (\uFEFF) to ensure Microsoft Excel displays special characters accurately
var blob = new Blob(['\uFEFF' + csvData], { type: 'text/csv;charset=utf-8;' });
var downloadUrl = URL.createObjectURL(blob);
var hiddenLink = document.createElement('a');
hiddenLink.href = downloadUrl;
hiddenLink.download = fileName;
hiddenLink.style.display = 'none';
document.body.appendChild(hiddenLink);
hiddenLink.click();
document.body.removeChild(hiddenLink);
URL.revokeObjectURL(downloadUrl);
}
})
- 1. Invoke Apex: Component requests sObject records asynchronously via
$A.enqueueAction(). - 2. Sanitize & Filter: Helper strips Salesforce metadata (such as the
attributeskey) and establishes column headers. - 3. Format Values: Escapes quotes and commas to maintain valid CSV syntax.
- 4. Generate File: Packages the CSV string into a UTF-8 Blob and triggers the browser's download prompt.
attributes Object: Whenever Apex returns an sObject list to Lightning, each record includes an internal attributes object (e.g., { type: "Contact", url: "..." }). Always filter this property out during iteration, or it will render as a raw [object Object] column in your spreadsheet.
- Server Responsibility: Fetch filtered data and enforce FLS via
WITH USER_MODE. - Client Responsibility: Transform arrays to CSV string, encode characters, and prompt download.
- Character Encoding: Prefix with
\uFEFF(BOM) to preserve non-ASCII symbols in Microsoft Excel. - Scalability Boundary: Ideal for datasets under 2,000 records. For enterprise exports exceeding 50,000 records, migrate to batch jobs, reports, or Bulk API.