Reading & Writing CSV
CSV (comma-separated values) is still one of the most common formats for exchanging tabular data — exports from spreadsheets, imports into accounting software, bulk data uploads. PHP's built-in fgetcsv() and fputcsv() handle the format's fiddly edge cases (commas and quotes inside fields, escaped quote characters) so you don't have to parse it with explode() and get it subtly wrong. This page covers reading with fgetcsv(), writing with fputcsv(), treating the first row as a header, the delimiter and enclosure parameters, and a full read-process-write example.
fgetcsv(): reading one row at a time
fgetcsv($handle) reads the next line from an open file handle and parses it into an array of fields, correctly handling values wrapped in quotes — including ones that contain commas or embedded newlines. It returns false once there are no more lines to read, which makes it a natural fit for a while loop.
fgetcsv-basic.php
<?php
$handle = fopen('contacts.csv', 'r');
if ($handle === false) {
die('Could not open contacts.csv');
}
while (($row = fgetcsv($handle)) !== false) {
print_r($row);
}
fclose($handle);Array
(
[0] => id
[1] => name
[2] => email
)
Array
(
[0] => 1
[1] => Priya Shah
[2] => priya@example.com
)Treating the first row as a header
CSV files usually use their first row as column names rather than data. Reading that row once with fgetcsv() before the main loop, and then combining it with each subsequent row via array_combine(), turns each row into an associative array keyed by column name — much easier to work with than numeric indexes you have to remember the meaning of.
csv-with-header.php
<?php
$handle = fopen('contacts.csv', 'r');
$header = fgetcsv($handle);
$rows = [];
while (($row = fgetcsv($handle)) !== false) {
$rows[] = array_combine($header, $row);
}
fclose($handle);
print_r($rows[0]);Array
(
[id] => 1
[name] => Priya Shah
[email] => priya@example.com
)fputcsv(): writing rows
fputcsv($handle, $fields) writes one array of values as a single, correctly quoted CSV line to an open handle — quoting any field that contains a comma, a quote character, or a newline, and escaping embedded quotes. Write the header row first, then loop over the data.
fputcsv-basic.php
<?php
$handle = fopen('export.csv', 'w');
fputcsv($handle, ['id', 'name', 'email']);
fputcsv($handle, [1, 'Priya Shah', 'priya@example.com']);
fputcsv($handle, [2, 'Tom "TJ" Lin', 'tom@example.com']);
fclose($handle);
echo file_get_contents('export.csv');id,name,email 1,"Priya Shah",priya@example.com 2,"Tom ""TJ"" Lin",tom@example.com
Delimiter and enclosure parameters
Both functions accept optional parameters after the required ones: a delimiter character (a comma by default, but semicolons are common in some locales, and tabs are used for TSV-style files), and an enclosure character (a double quote by default) used to wrap fields that need it. Matching these to whatever produced the file — or whatever will consume the file you're writing — is essential; a file exported with semicolons but read with the default comma delimiter will parse every row as a single field.
delimiter-enclosure.php
<?php
// Reading a semicolon-delimited file
$handle = fopen('european-export.csv', 'r');
$row = fgetcsv($handle, 0, ';');
print_r($row);
fclose($handle);
// Writing with a single-quote enclosure instead of double quotes
$out = fopen('custom.csv', 'w');
fputcsv($out, ['value with, a comma', 'plain value'], ',', "'");
fclose($out);Full example: read, process, write
A common pattern is reading a CSV, transforming or filtering the data, and writing the result to a new file. The example below reads a list of orders, keeps only the ones over a minimum amount, adds a computed column, and writes the result to a new CSV.
process-orders.php
<?php
$in = fopen('orders.csv', 'r');
$out = fopen('large-orders.csv', 'w');
$header = fgetcsv($in);
$header[] = 'tax';
fputcsv($out, $header);
while (($row = fgetcsv($in)) !== false) {
$data = array_combine(['id', 'customer', 'amount'], $row);
if ((float) $data['amount'] < 100) {
continue;
}
$tax = round((float) $data['amount'] * 0.08, 2);
fputcsv($out, [$data['id'], $data['customer'], $data['amount'], $tax]);
}
fclose($in);
fclose($out);
echo 'Processed orders written to large-orders.csv';Processed orders written to large-orders.csv
fgetcsv()reads and parses one row at a time from an open handle; it returnsfalseat end of file.Read the first row separately to use as a header, then
array_combine()it with each data row.fputcsv()writes one array as a correctly quoted CSV row.The delimiter and enclosure parameters must match the file's actual format, or rows silently parse incorrectly.
Combining
fgetcsv()andfputcsv()in one script is the standard way to filter or transform CSV data.