Detect a CSV Delimiter Automatically in PHP

A CSV delimiter cannot always be inferred perfectly, but a multi-record sample is safer than counting characters in the first line. Parse each candidate delimiter and score consistent column counts across nonblank records.

Last updated: September 26, 2026.

function detectDelimiter(string $path, array $candidates = [',', ';', "	", '|']): string
{
    $scores = array_fill_keys($candidates, []);
    $file = new SplFileObject($path, 'r');

    for ($sampled = 0; !$file->eof() && $sampled < 20;) {
        $line = $file->fgets();
        if (trim($line) === '') continue;
        $sampled++;

        foreach ($candidates as $delimiter) {
            $scores[$delimiter][] = count(str_getcsv($line, $delimiter, '"', ''));
        }
    }

    $ranked = [];
    foreach ($scores as $delimiter => $counts) {
        $frequency = array_count_values($counts);
        arsort($frequency);
        $columns = (int) array_key_first($frequency);
        $ranked[$delimiter] = $columns > 1 ? reset($frequency) * $columns : 0;
    }
    arsort($ranked);
    return (string) array_key_first($ranked);
}

The score rewards a candidate that repeatedly produces more than one column. Production code should reject a tie or zero score rather than silently choosing the first delimiter.

Account for quoted multiline fields

Sampling physical lines is imperfect when a quoted field contains line breaks. For high-trust imports, require the delimiter in the file contract or let the user confirm the detected value. After choosing, parse the actual file with SplFileObject::fgetcsv(), which can read logical CSV records.

Separate encoding from delimiter detection

A valid delimiter is one byte. If the sample is UTF-16, zero bytes may appear between ASCII characters and detection will fail. Detect or obtain the source encoding first, convert a sample to UTF-8, and then score delimiters. The existing CSV encoding guide covers BOMs and spreadsheet exports.

Validate after detection

  1. Reject ambiguous top scores or ask for confirmation.
  2. Parse the header and several data records.
  3. Check expected field-count ranges and required names.
  4. Log the chosen delimiter and confidence without logging sensitive rows.
  5. Allow an explicit override for known suppliers.

SplFileObject::getCsvControl() only returns the configured control characters; it does not inspect a file and guess them. Once the delimiter is approved, use it consistently in large-file processing or CSV splitting.

Related Web Cheat Sheet guides

Sergey Kornilov

Sergey Kornilov