The generateDynamicCSV extension creates a CSV file from a JSON object, JSON array, or JSON string and downloads it from the browser execution context. It automatically derives CSV headers from the provided records, escapes CSV values safely, applies a valid .csv file extension, and can append an ISO timestamp to the generated file name.
This is useful when a journey needs to export runtime variables, order details, customer data, generated test records, or other structured values into a downloadable CSV file for review, evidence capture, or downstream manual analysis.
Parameters:
-
csvDatarequired, a JSON object, JSON array of objects, or JSON string that can be parsed into an object or array of objects. Each CSV row must be a plain object; -
fileNameoptional, a string containing the CSV file name. If omitted or blank, the extension usesruntime_variables.csv. Invalid file-name characters are replaced with underscores, and.csvis added when missing; -
appendTimestampoptional, a boolean or boolean-like string. Values such astrue,yes, and1enable timestamp appending. Blank or missing values default totrue.
Note: The CSV headers are built dynamically from all object keys in the order they first appear. Missing values are exported as blank cells, and nested objects or arrays are converted to JSON strings before CSV escaping.
How to apply this to your journey
Use the extension in a journey by calling generateDynamicCSV with the required values in order: csvData, fileName, and appendTimestamp.
Note: This extension parses csvData before creating the file. Invalid JSON strings, empty input, arrays containing non-object rows, or objects with no headers cause the step to fail with a clear error message.
generateDynamicCSV({"Customer Name":"Acme Ltd","Order Number":"ORD-1001","Status":"Created","Created Date":"25/06/2026"}, "orders.csv", "true") returning $statusgenerateDynamicCSV([{"Customer Name":"Acme Ltd","Order Number":"ORD-1001","Status":"Created"},{"Customer Name":"Beta Ltd","Order Number":"ORD-1002","Status":"Approved"}], "order_export", "false") returning $statusYou can also pass stored journey variables into the JSON payload. When a value is a Virtuoso variable, pass it directly as $variableName and do not wrap it in quotes. For example, write $ssn, $email, $password, and $data, not "$ssn", "$email", "$password", or "$data". Quoting the variable makes it plain text instead of resolving the runtime value. The extension returns a result object that can be stored in a Virtuoso variable for later checks or logging.
generateDynamicCSV($data, "customer_data.csv", "false") returning $statusStore value ${{
"SSN": $ssn,
"Email": $email,
"Password": $password
}} in $data
generateDynamicCSV($data, "customer_data.csv", "false") returning $statusExample output returned by the extension:
{
"success": true,
"message": "CSV file generated successfully.",
"fileName": "customer_data.csv",
"headers": [
"SSN",
"Email",
"Password"
],
"totalRows": 1
}This extension does not require any external resource.
The extension should be configured as:
- Run asynchronously: No
- Scope: Global
Limitation: This extension creates a client-side CSV download using Blob, URL.createObjectURL, a temporary hidden anchor element, and the browser download flow available in the current journey execution context. It does not upload the file to Virtuoso, attach it to a result, or return the CSV content; it returns only status details such as the generated file name, headers, and row count. Very large csvData payloads can consume browser memory because the full CSV is built in memory before the download starts. The generated timestamp uses the execution browser's UTC ISO time rather than a user-selected local timezone. Cross-browser note: Because this extension relies on browser download behavior and object URLs, file download handling can differ between Virtuoso's default browser and cross-browser, mobile, or remote-grid executions. Validate it in each browser/device configuration used by your plan, especially where downloads are restricted, redirected, renamed, blocked by browser policy, or unavailable in the execution environment.
Add the extension to your Virtuoso instance
Select the domain that matches your Virtuoso account.
View source
Last updated: 25/06/2026
Resources:
This extension does not require any external resource.
/**
* Parameters
* csvData -> JSON object, JSON array, or JSON string
* fileName -> Optional CSV file name
* appendTimestamp -> Optional true/false flag
* Created By : Suhas, YS
* Created Date : 25/06/2026 11:04 AM IST
* Example Input
* {
"Customer Name": "$customerName",
"Order Number": "$orderNumber",
"Status": "$status",
"Created Date": "$createdDate"
}
*/
function parseBoolean(value, defaultValue) {
if (value === undefined || value === null || value === "") {
return defaultValue;
}
if (typeof value === "boolean") {
return value;
}
const normalizedValue = String(value).trim().toLowerCase();
return normalizedValue === "true" || normalizedValue === "yes" || normalizedValue === "1";
}
function normalizeCsvData(data) {
if (data === undefined || data === null || data === "") {
throw new Error("csvData input is required.");
}
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (error) {
throw new Error("csvData must be a valid JSON object, JSON array, or JSON string.");
}
}
const rows = Array.isArray(data) ? data : [data];
if (!rows.length) {
throw new Error("csvData does not contain any records.");
}
rows.forEach(function(row, index) {
if (!row || typeof row !== "object" || Array.isArray(row)) {
throw new Error("Each CSV row must be an object. Invalid row found at index " + index + ".");
}
});
return rows;
}
function escapeCSVValue(value) {
if (value === undefined || value === null) {
return "";
}
if (typeof value === "object") {
value = JSON.stringify(value);
}
value = String(value).replace(/"/g, '""');
if (
value.indexOf(",") !== -1 ||
value.indexOf('"') !== -1 ||
value.indexOf("\n") !== -1 ||
value.indexOf("\r") !== -1
) {
value = '"' + value + '"';
}
return value;
}
function buildCSV(rows) {
const headers = [];
rows.forEach(function(row) {
Object.keys(row).forEach(function(key) {
if (headers.indexOf(key) === -1) {
headers.push(key);
}
});
});
if (!headers.length) {
throw new Error("No CSV headers found. Please pass at least one label-value pair.");
}
const csvLines = [];
csvLines.push(headers.map(escapeCSVValue).join(","));
rows.forEach(function(row) {
const line = headers.map(function(header) {
return escapeCSVValue(row[header]);
}).join(",");
csvLines.push(line);
});
return {
headers: headers,
content: csvLines.join("\n")
};
}
function buildFileName(name, shouldAppendTimestamp) {
let finalFileName = name || "runtime_variables.csv";
finalFileName = String(finalFileName).trim();
if (!finalFileName) {
finalFileName = "runtime_variables.csv";
}
finalFileName = finalFileName.replace(/[\\/:*?"<>|]/g, "_");
if (!/\.csv$/i.test(finalFileName)) {
finalFileName += ".csv";
}
if (shouldAppendTimestamp) {
const timestamp = new Date()
.toISOString()
.replace(/[:.]/g, "-");
finalFileName = finalFileName.replace(/\.csv$/i, "");
finalFileName = finalFileName + "_" + timestamp + ".csv";
}
return finalFileName;
}
function downloadCSV(csvContent, finalFileName) {
const blob = new Blob([csvContent], {
type: "text/csv;charset=utf-8;"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = finalFileName;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
const rows = normalizeCsvData(csvData);
const csvResult = buildCSV(rows);
const finalFileName = buildFileName(fileName, parseBoolean(appendTimestamp, true));
downloadCSV(csvResult.content, finalFileName);
return {
success: true,
message: "CSV file generated successfully.",
fileName: finalFileName,
headers: csvResult.headers,
totalRows: rows.length
};
Comments
0 comments
Please sign in to leave a comment.