Get an XML export into a spreadsheet
XML is how a lot of software hands data over. Product feeds, order exports and the output of older databases all come as tags, and none of that opens as a table. Converting XML to CSV turns the file into rows and columns, so you can sort it, filter it and add it up in Excel, Google Sheets or Numbers.
The hard part isn't the comma between the values. It's deciding which part of the XML file is a row, and what to do with attributes and tags nested inside other tags, because a CSV file has no place for either. This page explains the choices our converter makes, with the file we ran through it.
How to convert XML to CSV
- Upload your XML file - Drag it into the box at the top of this page, or browse for it. You can add up to 5 files at once.
- Check the output - CSV is already picked on this page, so there's nothing to change.
- Download your file - Save the .csv file when it's ready. The download link works for 30 minutes.
The conversion runs on our servers, not in your browser. There's nothing to install and no account to make, and our security page explains what happens to your file after that.
How the converter turns tags into rows and columns
Which element becomes a row
A CSV is one table, so the converter has to pick one kind of element to be the rows. It starts at the root element and works down:
- If any child tag repeats, the most repeated tag gives the rows. A
<catalog>with ten<book>elements gives ten rows. If two tags repeat the same number of times, the one holding more data wins. - If nothing repeats, and every child is an element with children of its own, the converter steps into the child holding the most data and looks again. That's how it gets past a wrapper like a SOAP envelope or a
<response><data>layer. - Otherwise the element it reached is the one record, and the CSV has one row.
Only the most repeated kind of element becomes rows, and any other kind at the same level is left out. The backend team's production test on 13 November 2026 made this concrete: a file with 3 <book> and 2 <magazine> elements gave 3 rows of books, with no magazines. If you need every element in the file, XML to JSON keeps the whole structure instead of picking one table from it.
Column names
- Nested tags get dotted names. A
<city>inside<address>inside<customer>becomes the columncustomer.address.city. - Attributes get an
@_prefix, so<book id="bk101">gives a column named@_id. Attribute values are trimmed of spaces at both ends. - Text next to an attribute goes in a
#textcolumn.<weight unit="kg">2.5</weight>gives two columns,weight.@_unitandweight.#text, so the unit and the number stay apart. - Order: columns appear in the order each name is first seen, and an element's attributes come before its child tags. A column that only shows up in the fifth record is added at the end.
- Namespaces: a prefix stays part of the name, as in
ord:total. Thexmlnsdeclarations are left out, because they aren't data.
Repeated elements inside a record
Records often repeat something themselves, like the items on an order:
<orders>
<order id="7">
<customer>Ana</customer>
<item><sku>A1</sku><qty>2</qty></item>
<item><sku>B2</sku><qty>1</qty></item>
<tag>rush</tag>
<tag>gift</tag>
</order>
<order id="8">
<customer>Ben</customer>
<item><sku>C3</sku><qty>5</qty></item>
</order>
</orders>
Here <order> is the record, since it's the tag that repeats under the root. Inside it, three things can happen:
- The first repeated element that has children of its own,
<item>here, gives one row per item. Order 7 becomes two rows and order 8 one row, and the order's own fields, its@_idandcustomer, are repeated on each of its rows. - Repeated elements that hold only text, like the two
<tag>elements, share one cell, joined with a semicolon and a space:rush; gift. - If a record has a second repeated element with children, that one is kept as JSON text in a single cell, so the data isn't lost but the row count doesn't multiply.
We ran this file through our live converter on 13 November 2026, and it gave this CSV:
@_id,customer,item.sku,item.qty,tag
7,Ana,A1,2,rush; gift
7,Ana,B2,1,rush; gift
8,Ben,C3,5,
When the repeating element holds only text, as in a list of <email> elements, the tag name becomes the one column. If some of those elements also carry attributes, the text goes under #text.
What's kept and what's dropped
Every value in the CSV is text, exactly as written in the XML. 7.50 stays 7.50 and a code like 007 keeps its zeros. Text is trimmed at both ends, but line breaks inside it are kept. Built-in and numeric entities are decoded, so & and é come out as & and é, and CDATA sections are copied as written. Comments, processing instructions and the <?xml ?> line are dropped, since none of them is data.
A real example from our tests
On 13 November 2026 we uploaded this file to our live converter. It's a small book catalog, with two attributes on each book, a price that carries a currency attribute, and an ISBN that starts with a zero:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="bk101" lang="en">
<title>The Hobbit</title>
<author>J.R.R. Tolkien</author>
<price currency="USD">8.99</price>
<isbn>0261102214</isbn>
</book>
<book id="bk102" lang="fr">
<title>Le Petit Prince</title>
<author>Antoine de Saint-Exupéry</author>
<price currency="EUR">7.50</price>
<isbn>0156012197</isbn>
</book>
</catalog>
About 17 seconds after we started the upload, it came back as this CSV:
@_id,@_lang,title,author,price.@_currency,price.#text,isbn
bk101,en,The Hobbit,J.R.R. Tolkien,USD,8.99,0261102214
bk102,fr,Le Petit Prince,Antoine de Saint-Exupéry,EUR,7.50,0156012197
The <book> element repeats, so each book is a row. The two attributes became the first two columns, @_id and @_lang, and the price split into price.@_currency and price.#text. The ISBN 0261102214 kept its leading zero, because the converter never turns text into a number, and the accent in Saint-Exupéry came through. The file is UTF-8 with a BOM, and each line ends with a carriage return and a line feed, the Windows line ending. You can download the XML file we uploaded and the CSV it produced to compare them yourself.
The backend team's production tests the same day also covered an orders export with namespaces and CDATA, a file declaring ISO-8859-1 (São Paulo came out correctly), and a file with Japanese, Arabic, Hindi and emoji text, which all came through unchanged. Before launch, on our test server, an XML file under 20 MB converted to a CSV of 156,916 rows, and UTF-16 XML with and without a BOM was read correctly.
Opening the CSV in Excel
The CSV starts with a BOM, three bytes that tell Excel the text is UTF-8. Without it, Excel has to guess the encoding, and accents and other alphabets can turn into odd symbols. We checked the bytes of the file, but we haven't tested it inside Excel yet.
Excel can still change a value when you double-click a CSV. A number with a leading zero, like the ISBN above, is correct in the file, but Excel may show it as 261102214, because it treats anything that looks like a number as a number. To keep the zero, open the file through Data, then From Text/CSV, and set that column to Text before you load it.
You may also see an apostrophe at the start of a few cells. The converter adds one when text starts with =, +, -, @, a tab or a carriage return, so a spreadsheet shows it as text instead of running it as a formula. In the backend team's tests, =1+1 became '=1+1, @SUM(A1) became '@SUM(A1) and +x became '+x. A plain negative number like -42 is left alone.
Limits, and files that won't convert
- Each XML file can be up to 20 MB. The upload box accepts bigger files, but the converter stops anything over 20 MB and asks you to split it. An empty file is refused with
This file is empty. - The CSV can't go over 1,000,000 rows, 16,384 columns or 5,000,000 cells, which is roughly what a spreadsheet can hold. The XML itself can't have more than 2,000,000 elements or be nested more than 64 levels deep. Each limit has its own message saying which one you hit.
- The converted file must stay under 200 MB, and a conversion that runs past 960 seconds is stopped.
- The file must be UTF-8, or declare its encoding in the
<?xml ?>line. A file that is neither is refused withInvalid XML: the file isn't UTF-8 text and doesn't say which encoding it uses. - A DOCTYPE that declares entities is refused, for security, with
This XML declares entities, which we don't accept for security reasons.A plain<!DOCTYPE root>line is fine. - An XML file with no element that holds data gives
This file has no data to put in a table.
If the XML itself is broken, the message says what's wrong and where, as in Invalid XML: mismatched tag at line 3. Fix that line in a text editor and upload the file again.
Going the other way, CSV to XML turns a table back into tags. If your data is JSON rather than XML, JSON to CSV flattens it with the same dotted column names, and the JSON converter page lists the other formats it can go to.