PHPWriting & Appending Files

Writing & Appending Files

Writing to a file in PHP breaks down into two questions: do you want to replace the file's contents or add to them, and do you need to worry about another process writing to the same file at the same time? This page covers file_put_contents() as the everyday tool for both writing and appending, fopen('a') as the handle-based alternative, the LOCK_EX flag for safe concurrent writes, and how to write structured data like JSON and CSV to disk.

file_put_contents(): the simple case

file_put_contents($path, $data) writes a string to a file in a single call, creating the file if it doesn't exist and overwriting it completely if it does. It's the write-side equivalent of file_get_contents() — no fopen(), no fclose(), just a path and the data to write. It returns the number of bytes written, or false on failure.

put-contents-basic.php

PHP
<?php

$bytes = file_put_contents('greeting.txt', "Hello, world!\n");

if ($bytes === false) {
    die('Failed to write greeting.txt');
}

echo "$bytes bytes written";
14 bytes written
Appending with FILE_APPEND

Calling file_put_contents() a second time on the same path overwrites what was there before, which is rarely what you want for something like a log file. Passing the FILE_APPEND flag as the third argument changes that behavior: instead of truncating the file, PHP opens it in append mode and adds the new data to the end, leaving everything already there intact.

append-flag.php

PHP
<?php

$logLine = '[' . date('Y-m-d H:i:s') . '] User logged in' . PHP_EOL;

file_put_contents('app.log', $logLine, FILE_APPEND);
fopen('a'): the handle-based alternative

file_put_contents() with FILE_APPEND is really a shortcut for opening a handle in append mode, writing, and closing it. Reaching for fopen() directly makes sense when you're appending several pieces of data across multiple fwrite() calls without wanting to reopen the file each time, or when you're combining the write with other handle operations.

fopen-append.php

PHP
<?php

$handle = fopen('app.log', 'a');

if ($handle === false) {
    die('Could not open app.log for appending');
}

fwrite($handle, '[' . date('Y-m-d H:i:s') . '] Request started' . PHP_EOL);
fwrite($handle, '[' . date('Y-m-d H:i:s') . '] Request finished' . PHP_EOL);

fclose($handle);
LOCK_EX: safety under concurrent writes

If two requests write to the same file at nearly the same moment — common on a busy web server, where each request may run in its own process — their writes can interleave, producing a corrupted mix of both. LOCK_EX, passed as (part of) the flags argument to file_put_contents() or via flock() on a handle, asks the operating system for an exclusive lock before writing, so the second writer waits until the first one finishes instead of writing at the same time.

lock-ex-example.php

PHP
<?php

$entry = json_encode(['event' => 'signup', 'time' => time()]) . PHP_EOL;

file_put_contents('events.log', $entry, FILE_APPEND | LOCK_EX);
Locking is advisory, and only helps between PHP writers
`flock()`/`LOCK_EX` is an advisory lock: it only protects against other code that also checks the lock before writing. It doesn't stop a process that ignores locking entirely, and on some network filesystems (certain NFS configurations, for instance) locking behavior is unreliable. For high-throughput logging or multi-writer scenarios, a database or a dedicated logging service is usually a better fit than a shared flat file.
Writing structured data: JSON and CSV

Flat files often need to hold structured data rather than plain text. For JSON, encode the data first with json_encode() and write the resulting string like any other; JSON_PRETTY_PRINT is worth adding when a human might read the file later. For CSV, PHP's fputcsv() (covered fully on the CSV page) writes one row at a time to an open handle, correctly quoting fields that contain commas or quote characters.

write-json.php

PHP
<?php

$settings = [
    'theme' => 'dark',
    'notifications' => true,
    'retries' => 3,
];

file_put_contents(
    'settings.json',
    json_encode($settings, JSON_PRETTY_PRINT)
);

write-csv-preview.php

PHP
<?php

$handle = fopen('export.csv', 'w');

fputcsv($handle, ['id', 'name', 'email']);
fputcsv($handle, [1, 'Priya Shah', 'priya@example.com']);
fputcsv($handle, [2, 'Tom Lin', 'tom@example.com']);

fclose($handle);
Never build a save path from raw user input
If a filename comes from a form field, a query string, or an uploaded file's original name, don't drop it straight into a path like `"uploads/" . $_POST['filename']`. A crafted value such as `../../var/www/index.php` can escape the intended directory entirely (path traversal). Generate your own filename (a UUID, a hash, or a sanitized slug), or strictly validate the input against an allowed pattern before using it in a path.
  • file_put_contents() — write or overwrite a file in one call.

  • file_put_contents($path, $data, FILE_APPEND) — append instead of overwrite.

  • fopen($path, 'a') — the handle-based alternative for multiple appends.

  • LOCK_EX — request an exclusive lock to avoid interleaved writes from concurrent processes.

  • json_encode() + file_put_contents() for structured JSON output; fputcsv() for CSV rows.

Tip
Default to combining `FILE_APPEND | LOCK_EX` for anything that multiple requests might write to concurrently, such as a shared log file. It costs one extra flag and avoids a class of hard-to-reproduce corruption bugs that only show up under real production traffic.