DEV Community

Cover image for Convert CSV to Excel (XLSX) in JavaScript, in the Browser and in Node
Simran Kaur
Simran Kaur

Posted on

Convert CSV to Excel (XLSX) in JavaScript, in the Browser and in Node

You export a CSV, someone opens it in Excel, and 00123 turns into 123. A phone column becomes scientific notation. Dates flip from day-month to month-day depending on the machine's locale. If you have ever shipped a "data export" feature, you have gotten that bug report.

The root cause is simple: a CSV is plain text with no type information. When Excel opens it, it guesses what each column is. A real .xlsx file stores the type per cell, so once you write XLSX yourself you get to decide, and the guessing stops.

Here is how to convert CSV to Excel in JavaScript, both server-side in Node and fully in the browser, without losing your data along the way.

The one-liner that works for clean data

If your data is genuinely simple (no IDs with leading zeros, no long numbers, no ambiguous dates), the fastest path uses the xlsx package:

npm install xlsx
Enter fullscreen mode Exit fullscreen mode
const XLSX = require("xlsx");

// read the CSV straight into a workbook
const workbook = XLSX.readFile("employees.csv");

// write it back out as a real .xlsx
XLSX.writeFile(workbook, "employees.xlsx");
Enter fullscreen mode Exit fullscreen mode

Two lines of real work. readFile parses the CSV into a sheet, writeFile serializes it to XLSX. Done.

The catch: readFile still lets the parser coerce values. 00123 can still come out as the number 123. So for anything that matters, keep reading.

Keeping leading zeros, long numbers, and dates intact

The fix is to read the CSV as raw strings, then force the fragile columns to the text type before you write. Here is a version that treats an Employee ID column as text so zeros survive:

const XLSX = require("xlsx");
const fs = require("fs");

const csv = fs.readFileSync("employees.csv", "utf8");

// parse as strings, do not let the parser guess types
const workbook = XLSX.read(csv, { type: "string", raw: true });
const sheet = workbook.Sheets[workbook.SheetNames[0]];

// walk the cells and pin column A (Employee ID) to text
const range = XLSX.utils.decode_range(sheet["!ref"]);
for (let row = range.s.r + 1; row <= range.e.r; row++) {
  const addr = XLSX.utils.encode_cell({ r: row, c: 0 }); // column A
  const cell = sheet[addr];
  if (cell) {
    cell.t = "s";                 // s = string
    cell.v = String(cell.v);      // keep the original text, zeros and all
  }
}

XLSX.writeFile(workbook, "employees.xlsx");
Enter fullscreen mode Exit fullscreen mode

cell.t = "s" is the whole trick. It tells the XLSX writer "this is text, not a number," so Excel shows 00123 exactly as written instead of helpfully stripping the zeros.

The same idea covers the other classic breakages:

  • Long IDs (16+ digits): set them to text, or Excel renders 1.2E+15.
  • Dates: either keep them as text in a known format like YYYY-MM-DD, or write real date cells with cell.t = "d" and a cell.z number format so every machine reads them the same way.
  • Encoding: read the file as UTF-8 (as above) so accented names like Muller do not turn into symbols.

Doing it entirely in the browser

You do not need a server for this. The same library runs client-side, so the file never leaves the user's machine, which is the right call for anything private like a customer list.

<input type="file" id="file" accept=".csv" />
<script src="https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js"></script>
<script>
document.getElementById("file").addEventListener("change", async (e) => {
  const file = e.target.files[0];
  const text = await file.text();

  // parse the CSV as strings so nothing gets coerced
  const wb = XLSX.read(text, { type: "string", raw: true });

  // hand the user a real .xlsx download
  XLSX.writeFile(wb, file.name.replace(/\.csv$/i, "") + ".xlsx");
});
</script>
Enter fullscreen mode Exit fullscreen mode

file.text() reads the CSV, XLSX.read parses it, and writeFile in the browser triggers a download of the generated workbook. No backend, no upload, no temp files to clean up.

Batch converting a folder in Node

When you have a directory of CSVs to convert on a schedule, loop over them:

const XLSX = require("xlsx");
const fs = require("fs");
const path = require("path");

const dir = "./exports";
for (const name of fs.readdirSync(dir).filter((f) => f.endsWith(".csv"))) {
  const wb = XLSX.readFile(path.join(dir, name));
  XLSX.writeFile(wb, path.join(dir, name.replace(/\.csv$/i, ".xlsx")));
}
Enter fullscreen mode Exit fullscreen mode

Drop this in a cron job or a CI step and your CSV drops become XLSX automatically.

When you just need it once, without writing code

Not every CSV-to-Excel job deserves a script. If you are handing the task to a non-developer, or you just need a clean workbook once, a browser tool is faster than wiring up a project.

I built a free one for exactly this: Pixellize CSV to XLSX converter. Drop a .csv, review the parsed grid (it keeps leading zeros), edit any cell, and download a real .xlsx. It runs fully in the browser, so nothing is uploaded. There is also an XLSX to CSV converter and an Excel to JSON tool for the reverse trips.

If you want the non-code walkthrough with screenshots and the Excel-specific gotchas, I wrote that up here: How to convert CSV to Excel without breaking your data.

Takeaways

  • A CSV has no type information, so opening it in Excel means letting Excel guess. Writing XLSX yourself removes the guessing.
  • In JavaScript, XLSX.read plus XLSX.writeFile does the conversion in two steps, in Node or in the browser.
  • Read values as strings and set cell.t = "s" on fragile columns to keep leading zeros and long numbers.
  • Handle dates explicitly and read as UTF-8 so nothing silently changes.

Got a favorite approach for typed CSV imports? Drop it in the comments.

Top comments (0)