Reader Stacks

Working With Files and Folders in Laravel: Copy, Move, and Delete Recursively

File::deleteDirectory() removes a folder and everything inside it in one call — there's no built-in confirmation step, so a wrong path here is a silent, unrecoverable, one-shot operation.

Working With Files and Folders in Laravel: Copy, Move, and Delete Recursively

Three genuinely common local filesystem operations in Laravel — copying, moving, and recursively deleting a folder and its contents — are all handled through the File facade's own dedicated directory methods, distinct from the single-file methods covered elsewhere on this site.

Copying a single file

use Illuminate\Support\Facades\File;

File::copy(storage_path('app/original.pdf'), storage_path('app/backups/original-copy.pdf'));

Copying an entire directory recursively

File::copyDirectory(storage_path('app/source-folder'), storage_path('app/backup-folder'));

copyDirectory() copies the source folder's entire contents, including nested subfolders, into the destination — the destination folder is created automatically if it doesn't already exist.

Moving a file or folder

File::move(storage_path('app/temp/upload.pdf'), storage_path('app/final/upload.pdf'));

File::moveDirectory(storage_path('app/temp-folder'), storage_path('app/final-folder'));

Unlike copy/copyDirectory, move/moveDirectory remove the original — there's no separate source file or folder left behind afterward, which matters for a workflow like moving an upload from a temporary staging location into its permanent home.

Deleting a single file

File::delete(storage_path('app/temp/upload.pdf'));

// Deleting multiple files at once
File::delete([
    storage_path('app/temp/file1.pdf'),
    storage_path('app/temp/file2.pdf'),
]);

Deleting a folder and everything inside it, recursively

File::deleteDirectory(storage_path('app/temp-uploads'));

This is a genuinely destructive, one-shot operation with no built-in confirmation step or trash/undo mechanism — the folder and every file and subfolder inside it are removed immediately and permanently; double-checking the path variable's actual value before calling this in any automated or scripted context is worth the extra caution, since a wrong path here fails silently rather than throwing an obvious error.

Checking if a directory exists before an operation

if (File::isDirectory(storage_path('app/temp-uploads'))) {
    File::deleteDirectory(storage_path('app/temp-uploads'));
}

Creating a nested directory structure that doesn't exist yet

if (! File::exists(storage_path('app/exports/2026/09'))) {
    File::makeDirectory(storage_path('app/exports/2026/09'), 0755, true);
}

The third argument to makeDirectory(), set to true, enables recursive creation — without it, attempting to create a deeply nested path whose parent directories don't yet exist fails, since the method by default only creates the single final directory level, not the full chain of parents leading to it.

A practical use case: cleaning up temporary upload folders on a schedule

Schedule::call(function () {
    $tempFolders = File::directories(storage_path('app/temp-uploads'));

    foreach ($tempFolders as $folder) {
        if (File::lastModified($folder) < now()->subHours(24)->timestamp) {
            File::deleteDirectory($folder);
        }
    }
})->hourly();

This is a genuinely useful pattern for a feature that stages files temporarily before final processing — automatically cleaning up anything older than a set threshold prevents an ever-growing accumulation of abandoned temporary folders from consuming disk space indefinitely.