Back

CSV viewer and cleaner

Data conversion

Loading

Loading tool

Preparing the tool. The remaining time is not yet known.

All processing for this tool happens in your browser. Your input is not sent to a server.

About this tool

Inspect a CSV or TSV export before changing it. Choose one set of value columns by position, trim their outer whitespace, replace empty cells or exact text tokens, and optionally normalize dates with an explicit input and output format. Optional empty-row removal and composite-key deduplication run after those value changes. Compare the original and cleaned previews, review each operation’s counts and invalid-date examples, then download the result. The original parsed data stays unchanged so you can reset the rules and start again. Processing runs locally in a browser worker.

Common uses

  • Prepare a customer export by trimming names and IDs, replacing a known N/A token, and checking the affected cells before importing it elsewhere.
  • Convert a known DD/MM/YYYY date column to YYYY-MM-DD while retaining and reporting impossible dates instead of guessing or shifting time zones.
  • Remove entirely empty records and select first, last or no row from duplicate key groups, keeping 001 distinct from 1 and retaining source order.

How to use it

  1. 1.Paste delimited text or choose a UTF-8 file. Select comma or tab and state whether the first record is a header. Inspect the parsed table before enabling changes; blank or duplicate header names and unequal row lengths are allowed. Select columns by position so repeated names remain unambiguous.
  2. 2.Enable only the needed rules. One shared set of selected columns receives all enabled value rules: trim, empty/exact-token replacement and date normalization. Choose a replacement string and explicit input/output date patterns as needed. Select only date columns when enabling dates; unrelated nonempty text in those same columns is reported as invalid. The fixed order is trim → replacement → dates → empty-row removal → deduplication. Replacement matches are case-sensitive whole-cell strings; an empty replacement is permitted.
  3. 3.For deduplication, select one or more key columns and choose the first row, the last row, or removal of every row in a repeated-key group. Run cleaning and compare before/after rows and stage counts. Check unchanged invalid dates and retained source-row numbers. Changes to inputs or settings invalidate the old result; rerun from the original input. Reset removes the cleaning rules rather than applying an inverse transformation. Download the cleaned CSV/TSV or the JSON report containing row arrays. The input header setting controls whether CSV/TSV includes the unchanged source header; headerless input does not acquire an exported header. Data columns stay in source order. Keep formula protection enabled for spreadsheet review; raw export is an explicit choice with a warning. JSON preserves parsed strings and unequal row lengths. Clear removes the current input and results from the tool.

Worked CSV cleaning examples

Trim only selected columns

{
  "input": "id,name,note\n 001 , Alice , keep \n1,Bob, as is ",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      0,
      1
    ],
    "trim": true,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "id",
    "name",
    "note"
  ],
  "hasHeader": true,
  "columnCount": 3,
  "rows": [
    [
      "001",
      "Alice",
      " keep "
    ],
    [
      "1",
      "Bob",
      " as is "
    ]
  ],
  "summary": {
    "inputRows": 2,
    "outputRows": 2,
    "columns": 3,
    "trimmedCells": 2,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 2,
    "changedRows": 1
  },
  "sourceRows": [
    1,
    2
  ]
}

Select positions 1 and 2 (zero-based indexes 0 and 1 in this fixture). Non-breaking and full-width spaces disappear from id and name; the unselected note keeps its spaces. The ID stays the string 001.

Replace empty cells and one exact token

{
  "input": "id,value\n1,\n2,N/A\n3,null\n4\n5,n/a",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      1
    ],
    "trim": false,
    "nulls": {
      "enabled": true,
      "tokens": [
        "N/A"
      ],
      "replaceEmpty": true,
      "replacement": "(missing)"
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "id",
    "value"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "1",
      "(missing)"
    ],
    [
      "2",
      "(missing)"
    ],
    [
      "3",
      "null"
    ],
    [
      "4"
    ],
    [
      "5",
      "n/a"
    ]
  ],
  "summary": {
    "inputRows": 5,
    "outputRows": 5,
    "columns": 2,
    "trimmedCells": 0,
    "replacedCells": 2,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 2,
    "changedRows": 2
  },
  "sourceRows": [
    1,
    2,
    3,
    4,
    5
  ]
}

The existing empty cell and uppercase N/A become (missing). The missing trailing field in row 4 stays absent. Literal null and lowercase n/a are not selected tokens and remain strings.

Normalize day-first dates without repairing invalid values

{
  "input": "id,date\nA,29/02/2024\nB,29/02/2023\nC,31/04/2024\nD,01/02/2024\nE,\nF,1/2/2024",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      1
    ],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": true,
      "inputFormat": "DD/MM/YYYY",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "id",
    "date"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "A",
      "2024-02-29"
    ],
    [
      "B",
      "29/02/2023"
    ],
    [
      "C",
      "31/04/2024"
    ],
    [
      "D",
      "2024-02-01"
    ],
    [
      "E",
      ""
    ],
    [
      "F",
      "1/2/2024"
    ]
  ],
  "summary": {
    "inputRows": 6,
    "outputRows": 6,
    "columns": 2,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 2,
    "invalidDates": 3,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 2,
    "changedRows": 2
  },
  "sourceRows": [
    1,
    2,
    3,
    4,
    5,
    6
  ]
}

The leap day in 2024 and 1 February convert. The 2023 leap day, 31 April and non-padded 1/2/2024 remain unchanged and count as three invalid cells. An empty string is skipped, not reported as an invalid date.

Choose month-first meaning explicitly

{
  "input": "date\n01/02/2024\n12/31/2024",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      0
    ],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": true,
      "inputFormat": "MM/DD/YYYY",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "date"
  ],
  "hasHeader": true,
  "columnCount": 1,
  "rows": [
    [
      "2024-01-02"
    ],
    [
      "2024-12-31"
    ]
  ],
  "summary": {
    "inputRows": 2,
    "outputRows": 2,
    "columns": 1,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 2,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 2,
    "changedRows": 2
  },
  "sourceRows": [
    1,
    2
  ]
}

Here MM/DD/YYYY makes 01/02/2024 mean 2 January. Selecting DD/MM/YYYY would make that same string mean 1 February. The tool does not infer which convention the source uses.

Check century leap years and four-digit year limits

{
  "input": "date\n0001/01/01\n2000/02/29\n1900/02/29\n0000/01/01\n9999/12/31",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      0
    ],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": true,
      "inputFormat": "YYYY/MM/DD",
      "outputFormat": "DD/MM/YYYY"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "date"
  ],
  "hasHeader": true,
  "columnCount": 1,
  "rows": [
    [
      "01/01/0001"
    ],
    [
      "29/02/2000"
    ],
    [
      "1900/02/29"
    ],
    [
      "0000/01/01"
    ],
    [
      "31/12/9999"
    ]
  ],
  "summary": {
    "inputRows": 5,
    "outputRows": 5,
    "columns": 1,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 3,
    "invalidDates": 2,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 3,
    "changedRows": 3
  },
  "sourceRows": [
    1,
    2,
    3,
    4,
    5
  ]
}

Years 0001 and 9999 are supported. Year 2000 is a leap year; 1900 is not. Year 0000 is outside this tool’s date profile. The selected output pattern puts day first without using a time zone.

Remove empty records after trimming

{
  "input": "a,b\n  , \n,\n,keep\nx",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      0,
      1
    ],
    "trim": true,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": true,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "a",
    "b"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "",
      "keep"
    ],
    [
      "x"
    ]
  ],
  "summary": {
    "inputRows": 4,
    "outputRows": 2,
    "columns": 2,
    "trimmedCells": 2,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 2,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 2,
    "changedRows": 1
  },
  "sourceRows": [
    3,
    4
  ]
}

Trimming makes the first data row entirely empty; the second already is. Both are removed. The partially populated third row and short fourth row remain, with source row numbers 3 and 4.

Follow the fixed order and inspect stage counts

{
  "input": "date,note\n 01/02/2024 ,first\nN/A,replacement\n,\n01/02/2024,last",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [
      0
    ],
    "trim": true,
    "nulls": {
      "enabled": true,
      "tokens": [
        "N/A"
      ],
      "replaceEmpty": false,
      "replacement": "01/02/2024"
    },
    "dates": {
      "enabled": true,
      "inputFormat": "DD/MM/YYYY",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": true,
    "dedupe": {
      "enabled": true,
      "columns": [
        0
      ],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "date",
    "note"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "2024-02-01",
      "first"
    ]
  ],
  "summary": {
    "inputRows": 4,
    "outputRows": 1,
    "columns": 2,
    "trimmedCells": 1,
    "replacedCells": 1,
    "convertedDates": 3,
    "invalidDates": 0,
    "emptyRowsRemoved": 1,
    "duplicateRowsRemoved": 2,
    "duplicateGroups": 1,
    "changedCells": 3,
    "changedRows": 3
  },
  "sourceRows": [
    1
  ]
}

Only the date column is selected. Trim and N/A replacement run before date conversion, so rows 1, 2 and 4 acquire the same date key. Row 3 is removed as empty; first-row deduplication keeps row 1. Three dates changed even though only one result row remains. Stage counts overlap.

Keep the last member while retaining source order

{
  "input": "key,value\nA,first\nB,middle\nA,last\nC,tail\nB,final",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": true,
      "columns": [
        0
      ],
      "keep": "last"
    }
  }
}
{
  "headers": [
    "key",
    "value"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "A",
      "last"
    ],
    [
      "C",
      "tail"
    ],
    [
      "B",
      "final"
    ]
  ],
  "summary": {
    "inputRows": 5,
    "outputRows": 3,
    "columns": 2,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 2,
    "duplicateGroups": 2,
    "changedCells": 0,
    "changedRows": 0
  },
  "sourceRows": [
    3,
    4,
    5
  ]
}

The last A is source row 3 and the last B is row 5. C at row 4 remains between them. Output is A, C, B in retained source order, not A, B, C in key-group order.

Remove every row in repeated-key groups

{
  "input": "key,value\nA,first\nB,middle\nA,last\nC,tail\nB,final",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": true,
      "columns": [
        0
      ],
      "keep": "none"
    }
  }
}
{
  "headers": [
    "key",
    "value"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "C",
      "tail"
    ]
  ],
  "summary": {
    "inputRows": 5,
    "outputRows": 1,
    "columns": 2,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 4,
    "duplicateGroups": 2,
    "changedCells": 0,
    "changedRows": 0
  },
  "sourceRows": [
    4
  ]
}

The none policy means keep no member of a duplicate group, not disable deduplication. Both A rows and both B rows disappear; only the unique C row remains.

Keep composite strings, leading zeros and missing keys distinct

{
  "input": "region,id\nnorth,001\nnorth,1\nsouth,001\nnorth,001\nnorth\nnorth,\na|b,c\na,b|c",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": true,
      "columns": [
        0,
        1
      ],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "region",
    "id"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "north",
      "001"
    ],
    [
      "north",
      "1"
    ],
    [
      "south",
      "001"
    ],
    [
      "north"
    ],
    [
      "north",
      ""
    ],
    [
      "a|b",
      "c"
    ],
    [
      "a",
      "b|c"
    ]
  ],
  "summary": {
    "inputRows": 8,
    "outputRows": 7,
    "columns": 2,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 1,
    "duplicateGroups": 1,
    "changedCells": 0,
    "changedRows": 0
  },
  "sourceRows": [
    1,
    2,
    3,
    5,
    6,
    7,
    8
  ]
}

Only source row 4 repeats the full north + 001 key. The strings 001 and 1 differ; north with a missing id differs from north with an empty id. Tuples [a|b, c] and [a, b|c] cannot collide by separator concatenation.

Preserve duplicate headers, ragged rows and quoted text

{
  "input": ",name,name\n001,\"A, \"\"B\"\"\",\"line1\nline2\"\n002,x\n003,,tail,extra",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "",
    "name",
    "name"
  ],
  "hasHeader": true,
  "columnCount": 4,
  "rows": [
    [
      "001",
      "A, \"B\"",
      "line1\nline2"
    ],
    [
      "002",
      "x"
    ],
    [
      "003",
      "",
      "tail",
      "extra"
    ]
  ],
  "summary": {
    "inputRows": 3,
    "outputRows": 3,
    "columns": 4,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 0,
    "changedRows": 0
  },
  "sourceRows": [
    1,
    2,
    3
  ]
}

Blank and repeated header names remain exact. The longest data row makes four columns even though the header has three. Quoted commas, escaped quotes and embedded newlines are parsed as cell text; short rows are not padded in the stored result.

Clean a headerless TSV by position

{
  "input": "001\t x \n1\ty\n001\t x ",
  "parseOptions": {
    "delimiter": "\t",
    "header": false
  },
  "options": {
    "columns": [
      1
    ],
    "trim": true,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": true,
      "columns": [
        0
      ],
      "keep": "first"
    }
  }
}
{
  "headers": [
    "column1",
    "column2"
  ],
  "hasHeader": false,
  "columnCount": 2,
  "rows": [
    [
      "001",
      "x"
    ],
    [
      "1",
      "y"
    ]
  ],
  "summary": {
    "inputRows": 3,
    "outputRows": 2,
    "columns": 2,
    "trimmedCells": 2,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 1,
    "duplicateGroups": 1,
    "changedCells": 2,
    "changedRows": 2
  },
  "sourceRows": [
    1,
    2
  ]
}

Tab is selected explicitly and the first record remains data. Generated column1 and column2 are labels, not an inserted CSV header. Trim column 2, then deduplicate column 1; 001 and 1 remain separate identifiers.

Export protected CSV without changing the cleaned values

{
  "input": "=header,value\n=1+1,001\n@cmd,\"line1\nline2\"\n  +2,",
  "parseOptions": {
    "delimiter": ",",
    "header": true
  },
  "options": {
    "columns": [],
    "trim": false,
    "nulls": {
      "enabled": false,
      "tokens": [],
      "replaceEmpty": false,
      "replacement": ""
    },
    "dates": {
      "enabled": false,
      "inputFormat": "YYYY-MM-DD",
      "outputFormat": "YYYY-MM-DD"
    },
    "removeEmptyRows": false,
    "dedupe": {
      "enabled": false,
      "columns": [],
      "keep": "first"
    }
  },
  "export": {
    "format": "csv",
    "safe": true,
    "delimiter": ","
  }
}
{
  "headers": [
    "=header",
    "value"
  ],
  "hasHeader": true,
  "columnCount": 2,
  "rows": [
    [
      "=1+1",
      "001"
    ],
    [
      "@cmd",
      "line1\nline2"
    ],
    [
      "  +2",
      ""
    ]
  ],
  "summary": {
    "inputRows": 3,
    "outputRows": 3,
    "columns": 2,
    "trimmedCells": 0,
    "replacedCells": 0,
    "convertedDates": 0,
    "invalidDates": 0,
    "emptyRowsRemoved": 0,
    "duplicateRowsRemoved": 0,
    "duplicateGroups": 0,
    "changedCells": 0,
    "changedRows": 0
  },
  "sourceRows": [
    1,
    2,
    3
  ],
  "exported": "\"'=header\",\"value\"\r\n\"'=1+1\",\"001\"\r\n\"'@cmd\",\"line1\nline2\"\r\n\"'  +2\",\"\""
}

The cleaned rows still contain their original strings. Only protected CSV serialization adds an apostrophe to the formula-like header and cells, including +2 after leading spaces. Fields are quoted and records use CRLF. This mitigation does not force spreadsheets to keep 001 as text.

Common CSV cleaning mistakes

  • Selecting a column by a repeated header name instead of checking its position.
  • Expecting trim to remove internal spaces, zero-width characters or every Unicode formatting character.
  • Treating missing trailing cells, empty strings and literal null text as the same value.
  • Choosing MM/DD/YYYY for day-first data, or assuming invalid dates are silently repaired.
  • Forgetting that trim or replacement can make formerly different deduplication keys equal.
  • Choosing remove-all duplicates when one representative row should remain.
  • Adding stage counts together as though each affected cell or removed row were counted only once.
  • Opening untrusted raw CSV in a spreadsheet or assuming quoting preserves leading-zero identifiers.

Limits and notes

  • Input is limited to 2 MiB of file bytes and 2 MiB of decoded UTF-8 text, 10,000 data rows, 128 columns and 200,000 cells. Exports and the intermediate transformed-data budget are capped at 12 MiB; a large replacement may be rejected before later row removal. Exceeding a limit blocks the operation rather than silently truncating data. Worker execution, bounded previews, cancellation and time limits reduce load but are not a hard browser memory sandbox. Use a local data tool for larger files. Comma and tab are explicit delimiters; detection is not automatic. UTF-8 files may have a BOM. Malformed quoting or invalid UTF-8 fails. Quoted separators, doubled quotes, multiline fields, blank or duplicate headers, headerless input and ragged rows are supported. A missing trailing field stays missing; display padding is not a new empty value. Headers are not cleaned, renamed or deduplicated.
  • Trim uses ECMAScript trim() whitespace at the ends of selected existing cells, including non-breaking and full-width spaces. It does not collapse internal spaces, remove all invisible characters, normalize Unicode or change case. Replacement compares the post-trim whole string exactly and runs once per matching cell. CSV has no null type: empty text, a missing cell, and the literal strings null or N/A are different. Missing cells are never filled; literal tokens change only when explicitly selected. Stage counts increase only when that stage changes a string; a valid date already in the output format or a same-value replacement is not a change. They are not distinct cells across the whole pipeline. A cell may be trimmed and replaced, then its row removed later. Net changed-cell and changed-row counts compare final normalized values with the original before row removal. The original input is retained independently; reset and reruns recompute from it, so edits do not accumulate. You cannot apply one column selection and then accumulate another selection’s changes. This is a deterministic rule pipeline, not an undo history, fuzzy matching, number conversion, case folding or automatic error correction.
  • Dates accept only YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY or YYYY/MM/DD, with exact field widths and a real proleptic Gregorian calendar date in years 0001–9999. Input and output patterns are explicit. There is no locale guessing, timestamp parsing, time-zone conversion or automatic interpretation of 01/02/2024. Invalid nonempty selected cells remain unchanged and are counted with bounded examples; empty strings and missing cells are skipped. Enabling trim can make outer-spaced dates eligible.
  • Empty-row removal runs after value normalization and removes a record only when every existing cell is the empty string. Whitespace is not empty unless trimming removes it; replacement with a nonempty value can prevent removal. Deduplication compares composite tuples of selected positional columns after all earlier steps. It preserves string differences such as 001 versus 1 and distinguishes missing from empty key components. First/last keep the chosen member; the remove-all policy retains only unique groups. Retained rows remain in their original source order. Preview search, filters and sorting affect only the displayed rows; exports contain all retained rows in source order.
  • Protected CSV adds an apostrophe to formula-like fields and therefore changes their exported text. Raw CSV can execute formulas when opened in a spreadsheet; quoting alone is insufficient, and saving/reopening may remove protection. Spreadsheet imports can also reinterpret leading zeros. JSON arrays are preferable for exact-string interchange and preserve missing trailing cells as shorter arrays, not null values. The JSON report includes hasHeader, headers, columnCount and rows; it does not map data into objects keyed by potentially duplicate header names. The input header setting governs CSV/TSV header inclusion, and column order is unchanged. Neither export preserves original quoting, encoding bytes or record separators. This tool does not upload or persist input data; downloaded files remain outside its control.

Frequently asked questions

Does reset restore data removed by cleaning?

Yes, within the current input. Cleaning does not overwrite the original parsed rows. Reset clears the rules and results so the original can be viewed or processed again. Changing rules and rerunning also starts from that original. Clear removes the input and results; it is not a recoverable edit history.

What does 01/02/2024 become?

With DD/MM/YYYY input and YYYY-MM-DD output it becomes 2024-02-01. With MM/DD/YYYY input it becomes 2024-01-02. The tool never chooses between them. Impossible dates such as 31/02/2024 stay unchanged and appear in the invalid-date count.

How are duplicate rows chosen and ordered?

Choose the key columns by position; all selected values form one composite key after the earlier cleaning stages. First retains the earliest row, last retains the latest row, and remove-all drops every row in groups with repeated keys. Retained rows are emitted in source order rather than key-group order. The strings 001 and 1 and missing versus empty components remain distinct.

Is protected CSV safe to open in every spreadsheet?

No universal guarantee is possible. The default apostrophe prefix mitigates formula interpretation and intentionally changes risky fields. Raw export removes that mitigation, and spreadsheet applications may reinterpret values or drop protection after saving. Review untrusted data and prefer JSON when exact strings matter.

Related tools