CSV Validate & Format
Verify CSV against RFC 4180, see exactly which rows are broken, view it as a sortable table, and format or convert it. Runs in your browser.
Where your data goes
| Using this page | Using the API, MCP or A2A | |
|---|---|---|
| Where it runs | In your browser. The CSV is parsed, checked, typed and re-formatted by JavaScript on this page; nothing you paste or load is sent anywhere. | In a Cloudflare Pages Function at the Cloudflare data centre nearest the caller. |
| What leaves your device | Nothing you enter. After the page and its scripts load, no request
carries your input. The browser enforces this: the page's Content-Security-Policy
sets connect-src 'none' and form-action 'none', which
block fetch, XHR, WebSocket, EventSource, beacons and form posts.
| The server receives the CSV text, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential data use the page, which does the same work in your browser and sends nothing. The server receives the JSON text, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential data use the page, which does the same work in your browser and sends nothing. |
| Stored | Nothing leaves the page. No cookies, analytics or third-party scripts; the browser keeps only your View → Inline config choice. | Your inputs and results: never. What is stored is your client record (client ID, a SHA-256 hash of your secret, expiry, and the profile you registered with) and one metadata row per call: operation, time, transport, status, error code, duration, request and response sizes, and country. |
| Logged | Nothing (no requests are made). | Only the per-call metadata above, attributed to your client ID. Never parameters, bodies, results, or credentials. Cloudflare, as the host, also processes request metadata (IP address, full URL including the query string, headers) under its own policies, so put inputs in POST bodies. |
| Access | Open to anyone. | Requires a client ID and secret (how to get one). HTTPS only; TLS terminates at Cloudflare. |
Details and how to verify this yourself: Security & privacy.
User guide
The studio works like a desktop window: a menu bar (File, Edit, View, Settings, Help) with the full-screen icon at the right of the same row. Settings → Options… opens the options in an overlay (or View → Inline config shows them in the card), and Help → Keyboard shortcuts… lists the keys.
- Paste CSV into the Input view, or choose File → Load sample to see one with a few problems. It is checked as you type; line numbers make positions easy to find.
- Read the verdict and the Problems view (the button at the right of the status line shows the count). Each problem has a severity, a line and column, and a message saying how to fix it. Click one to jump to it: in the input, the text is selected; in the Table view, the bad row is highlighted.
- Look at the Table view: a grid of the data with each column’s inferred type (integer, number, boolean, date, datetime, text, mixed or empty). Click a column heading to sort by it. Sorting only changes the view, not your data.
- Edit → Format (or Ctrl+Enter) writes the Formatted view: the same data with one delimiter, one quoting style and one line ending, without blank lines or a byte-order mark, and with exactly one line break at the end.
- Copy or Download the result, or File → Clear input to start again. The full-screen icon at the right of the menu bar makes the studio fill the window; Esc returns.
Options.
- Delimiter: Auto (detected), comma, semicolon, tab or pipe. Detection picks the delimiter that splits the first 50 rows into the same number of fields without breaking any quotes; set it yourself if it guesses wrong.
- Header: Auto, Yes or No. Auto treats the first row as a header when its values are all text (no numbers, true/false or dates), all different, and none of them appears again in its column below.
- Quoting (for the formatted output): Minimal quotes only values that need it; All quotes every value; Non-numeric quotes everything except numbers, which some importers use to tell text from numbers.
- Output delimiter: Same as input, or another delimiter to convert, e.g. semicolons to commas.
- Line ending: LF (the default; what most tools write) or CRLF (what RFC 4180 specifies and some older Windows software expects). Readers accept either.
- Trim: remove spaces and tabs around unquoted values. Quoted values are never trimmed: quoting is how a file says the spaces matter.
What the checks catch. The errors are the problems that change how a file is read: an unclosed quote swallows every row after it into one value; a quote in the middle of an unquoted value, or text after a closing quote, is read differently by different programs; a row with a missing or extra field shifts every value after it into the wrong column. The warnings are things that are probably mistakes, such as a, b (the space is part of the value, so the column is " b", not "b"), duplicate or empty header names, and blank lines. The info notes are worth knowing but often deliberate: a column that mixes numbers with N/A, quoting that varies from row to row, a delimiter at the end of every row, and values that a spreadsheet would run as a formula.
Formatting never changes your values. Only the delimiter, quotes, line breaks between rows, blank lines and padding outside quotes change. A value with leading or trailing spaces is quoted in the output, so tools that trim unquoted values keep the spaces. The exception is Trim, which you ask for. Formatting a file twice gives the same result as formatting it once.
Spreadsheet formulas. When a CSV is opened in Excel, LibreOffice or Google Sheets, a value starting with =, +, - or @ can be run as a formula; =HYPERLINK(...) or worse in data from a web form is a known attack (“CSV injection”). The page flags such values, unless they are plain numbers like -5. It does not change them: escaping is for the program that exports data to spreadsheets, usually by putting a ' in front.
Specification
Grammar. RFC 4180, read leniently and reported strictly.
- A file is a sequence of records separated by line breaks; a record is a sequence of fields separated by the delimiter. The last record may or may not end with a line break.
- A field is either unquoted (any characters except the delimiter,
"and line breaks) or quoted:"…", where the content may include the delimiter and line breaks, and a quote is written"". - Spaces are part of a field.
a , bis the two values"a "and" b".
Beyond RFC 4180. These are accepted:
- Delimiters
;, tab and|as well as,. - LF and lone CR line breaks as well as CRLF, and a mix of them (reported by
line-endings). - Spaces or tabs between a delimiter and a quoted field (
a, "b"): they are ignored and reported bywhitespace. A tab is never padding in a tab-separated file. - A byte-order mark: removed before parsing, so it never becomes part of the first header name.
- Rows with different numbers of fields are kept as they are (and reported).
- After an error that leaves the structure readable, parsing continues so every problem is listed: a stray quote stays in the value, and text after a closing quote is appended to it. Only an unclosed quote stops parsing, because everything after it is part of one value; then there is no formatted output.
Blank lines. A blank line between rows is skipped, with a blank-line warning; blank lines at the end are reported by final-newline. In a file with a single column an empty line is an empty value instead (there is no other way to write one), and the formatter writes it as "" so it cannot be mistaken for a blank line.
Delimiter detection (when Auto). Each candidate (, ; tab |) is used to parse the first 50 records (and at most the first 32,768 characters), quotes respected. Candidates that produce quote errors lose to those that do not; among the rest, the one whose most common field count covers the most records wins, and a tie goes to more fields, then to the order listed. A candidate must give at least two fields. If none does, the file is read as one column with , (and delimiterDetected is false). Limits: a file where several delimiters split every row consistently (a;b,c on every line) is read with the earliest-listed one; a file whose first 50 rows are not typical can mislead it.
Header detection (when Auto). The first row is a header when its non-empty values are all text (see the types below), are all different, at least one is non-empty, and none of them appears again in the same column of the next 49 rows. Limits: a file of text with no header (Ada,London) is read as having one, because the two cannot be told apart; a header containing a year or other number is read as data. Set Header when it matters.
Column count. The header’s length, or without a header the most common row length (ties go to the first seen). Each row with a different length gets a field-count error.
Rules. Every finding has a rule ID, a severity, a message, and the 1-based line and column (in Unicode code points) and 0-based offset (in UTF-16 units) where it starts, with an end position when it covers a range. A line break inside a quoted value starts a new line for positions, as it does in an editor. At most 100 findings are listed per rule, followed by one saying how many more there are.
| Rule | Severity | Fixed by Format | Reported when |
|---|---|---|---|
unclosed-quote | error | no | A quoted field never closes. At the opening quote. There is no formatted output. |
stray-quote | error | yes | A " appears inside an unquoted field (5" disk). At the first such quote; Format quotes the field and doubles it. |
text-after-quote | error | yes | Text follows a closing quote before the delimiter ("abc"def). From the closing quote to the end of the field; Format writes the joined value. |
field-count | error | no | A row has more or fewer fields than expected. Covers the row; the message gives its line and both counts. |
empty-header | warning | no | A header name is empty or only whitespace (except the extra column from trailing-delimiter). |
duplicate-header | warning | no | A header name repeats an earlier one exactly. At the repeat; the message names the first column. |
whitespace | warning or info | partly | Spaces or tabs around a value. Warning when they touch a delimiter (a, b), make up the whole value, or sit outside quotes (Format removes the last kind); info at the start or end of a line. Not reported inside quotes, or for unquoted values when Trim is on. |
blank-line | warning | yes | An empty line between rows (except in a one-column file). |
trailing-delimiter | info | no | Every row ends with an empty, unquoted field: probably a delimiter after the last value. Format keeps the column, since it is data. |
line-endings | warning | yes | Rows end with more than one of LF, CRLF and CR. At the first line break of the least used style. Line breaks inside quotes are values and are not counted. |
byte-order-mark | info | yes | The text starts with U+FEFF. Info, not a warning: Excel needs it to open UTF-8 CSV correctly. Format removes it; add it back if your users open the file in Excel. |
final-newline | info | yes | The last row has no line break (RFC 4180 allows this), or blank lines follow it. |
mixed-types | info | no | A column’s non-empty values have more than one type (integers and decimals count as numbers; dates and datetimes as datetimes). At the first value that differs from the most common type. |
inconsistent-quoting | info | yes | In one column (header excluded), some values are quoted although they need not be, while others are unquoted. At the first value of the less common style. |
formula-injection | info | no | A value (header included) starts with =, +, -, @, a tab or a CR, is at least two characters long, and is not a number. |
The shared text rules for the other formats are adapted: trailing spaces are part of the last value in CSV, so they are reported by whitespace instead of trailing-spaces; line length is not checked, because long rows are normal; mixed line endings and the final newline only count line breaks between rows.
Types. Values are typed after removing surrounding spaces; empty values are ignored, and a column’s type comes from all of its non-empty values.
| Type | Matches |
|---|---|
integer | An optional sign and digits, without leading zeros: 0, -12, +7. 007 and 00501 are text, because leading zeros mean an identifier (a ZIP code, an account number). |
number | An integer or decimal with an optional exponent: 1.5, .5, -2e10. A column of integers and decimals is number. Not numbers: 1., 1,000, 1_000, 0x1F, NaN, Infinity, $5, 5%. |
boolean | true or false, in any case. yes/no and 1/0 are not booleans. |
date | YYYY-MM-DD, a real calendar date (2024-02-29 yes, 2023-02-29 no). |
datetime | A date, T or a space, HH:MM, optional :SS and fraction, optional Z or ±HH:MM. A column of dates and datetimes is datetime. |
text | Anything else. |
mixed | Non-empty values of more than one of the above. |
empty | Every value is empty. |
Column statistics count empty values (missing fields in short rows included), distinct non-empty values, and the longest value in code points.
Formatting. The header (if any) and the data rows are written with the output delimiter and line ending, one line break after every row. Minimal quoting quotes a value that contains the delimiter, a quote, CR or LF, or starts or ends with a space or tab. All quotes every value, empty ones too. Non-numeric quotes every value that is not an integer or number (empty values included). Quotes inside values are doubled. A row consisting of one empty value is written as "".
Conversions.
- CSV → JSON: reads the CSV with the same rules and refuses one with errors. Objects (needs a header) gives one object per row, keyed by header name; an empty name becomes
column_Nand a repeated one gets_2,_3. Arrays gives one array per row, the header row first. With types,integerandnumbercolumns become JSON numbers (every digit kept, even beyond 2^53;+1and.5become1and0.5),booleancolumns becometrue/false, and empty values becomenull; text, date and mixed columns stay strings. - JSON → CSV: an array of objects gives a header of every key in first-seen order and one row per object (a missing key gives an empty value); a single object gives one row; an array of arrays gives rows without a header. Strings are written as they are, numbers exactly as written in the JSON,
true/falseas words,nullas an empty value, and nested objects and arrays as compact JSON text, with a note saying which columns contain them.
API.
POST /api/csv/formatwithinputand the options asdelimiter(auto,comma,semicolon,tab,pipe),header,quoting,output_delimiter,line_endingandtrim_fields. It returnsvalid, asummaryof counts, everydiagnostic, thedialectthat was used, the header, the column count and statistics, apreviewof the first 20 rows, andformatted. Invalid CSV is a 200 response withvalid: false, not an HTTP error.POST /api/convert/csv-to-json(shape,types) andPOST /api/convert/json-to-csv(delimiter,header,quoting,line_ending). They return 422 for CSV with errors or JSON of the wrong shape.inputmay be at most 100,000 characters forcsv_format, 50,000 forcsv_to_json(it also builds and serializes JSON) and 200,000 forjson_to_csv, which keeps each call within the server’s CPU budget. The page has no limit.
Lint rules
| Rule | Severity | What it checks | Fixed by Format |
|---|---|---|---|
unclosed-quote | error | A quoted field is never closed: everything after its opening quote is swallowed into one value. | no |
stray-quote | error | A double quote inside an unquoted field. Quote the whole field and double the quote ("5"" disk"). | yes |
text-after-quote | error | Text between a closing quote and the next delimiter or line break ("abc"def). | yes |
field-count | error | A row has more or fewer fields than the header (or, without a header, than most rows). | no |
empty-header | warning | A header name is empty. | no |
duplicate-header | warning | Two columns have the same header name. | no |
whitespace | warning | Spaces or tabs around a value. Warning when they pad a delimiter (a, b) or sit outside quotes; info at the start or end of a line. Format removes padding outside quotes; whitespace inside an unquoted value is data, removed only with the trim option. | no |
blank-line | warning | An empty line between rows. Ignored when reading; Format removes it. | yes |
trailing-delimiter | info | Every row ends with a delimiter, giving an extra empty column. | no |
line-endings | warning | Rows end with a mix of LF, CRLF and CR. Line breaks inside quoted values are data and are not counted. | yes |
byte-order-mark | info | The text starts with a byte-order mark. Excel uses it to recognise UTF-8; most other tools read it as part of the first header name. Format removes it. | yes |
final-newline | info | The last row has no line break, or is followed by blank lines. RFC 4180 allows either form for the last row; Format always ends with one line break. | yes |
mixed-types | info | A column mixes kinds of value, e.g. numbers and text (N/A). | no |
inconsistent-quoting | info | In one column, some values are quoted without needing it and others are not. | yes |
formula-injection | info | A value starts with =, +, -, @, tab or carriage return and is not a number: spreadsheets may run it as a formula. | no |
Errors make the document invalid. Warnings and information are style findings; the document still parses.
API & MCP
The same logic is available over HTTP and as MCP tools. Unlike this page, these run on the server: see where your data goes.
/api/csv/format Check CSV against RFC 4180, detect the delimiter and header, infer column types, list every problem with its position, and rewrite it consistently. GET POST /api/convert/csv-to-json Convert CSV to a JSON array of objects (keyed by the header) or of arrays, optionally typing numbers and booleans. GET POST /api/convert/json-to-csv Convert a JSON array of objects or of arrays to CSV, with the union of keys as the header.