Storage::delete

Deletes one path or an array of paths from a disk, treating an already-absent file as success.

Signature
Storage::delete(string|array $paths): bool
  • Added in Laravel 5.0

Parameters

ParameterTypeDescription
$$pathsstring|arrayA single path or an array of paths, all relative to the disk's root. Additional paths may also be passed as extra arguments.

Return value

booltrue when every path was deleted or was already absent; false when at least one deletion failed and the disk is not configured to throw.

Errors

Returns false on failure by default. When the disk config sets 'throw' => true, a League\Flysystem\UnableToDeleteFile is thrown.

Storage::delete removes files. It accepts one path or an array, and on a driver that supports it the array form is cheaper than a loop.

Deletion is idempotent

Deleting a file that is not there is not an error — Flysystem's contract says a delete succeeds if the file is absent afterwards, however it got that way. So true means "it is gone", not "it was there and I removed it". If you need to know whether something existed, ask exists first, accepting the extra round trip and the race.

This is the opposite of Cache::forget, which returns false for a key that was already missing. The two are easy to confuse when both appear in the same cleanup routine.

What trips people up

It deletes files, not directories. Storage::delete('avatars') does nothing useful; use Storage::deleteDirectory('avatars'), which removes the directory and everything in it.

A genuine failure — a read-only disk, an S3 policy denying DeleteObject — returns false silently. Since a missing file also returns true, an unchecked delete can leave orphaned objects accruing storage costs for years. Set 'throw' => true on disks where that matters.

The array form stops at nothing: one bad path does not prevent the others being removed, it only turns the return value false.

Example

Example · php
<?php

use Illuminate\Support\Facades\Storage;

Storage::disk('public')->put('avatars/7.png', 'binary');

// true: the file was removed.
var_dump(Storage::disk('public')->delete('avatars/7.png'));

// ALSO true: it was already gone. Deletion is idempotent.
var_dump(Storage::disk('public')->delete('avatars/7.png'));

// Several paths in one call.
var_dump(Storage::disk('public')->delete([
    'avatars/7.png',
    'avatars/7-thumb.png',
    'avatars/7-large.png',
]));

// Files only. For a whole folder:
Storage::disk('public')->deleteDirectory('avatars/2023');

echo Storage::disk('public')->missing('avatars/7.png') ? 'gone' : 'still there', PHP_EOL;

When to use it

  • Remove an avatar and its generated thumbnails in one call when a user uploads a replacement.
  • Clean up an export file from S3 after a queued job has emailed it.
  • Delete the attachments belonging to a model from a deleting event so nothing is orphaned.

Frequently asked questions

Why does Storage::delete return true for a file that does not exist?

Because deletion is defined by the end state: after the call the file is not there, so the operation succeeded. Flysystem does not treat an absent file as an error. If you need to know whether the file was present, check Storage::exists first and accept the extra round trip.

Can Storage::delete remove a directory?

No, it only removes files; use Storage::deleteDirectory($path), which deletes the directory and everything inside it recursively. Calling delete on a directory path returns true without removing anything, which reads like success.

How do I delete many files efficiently?

Pass an array of paths in a single call rather than looping. One failing path does not abort the rest — it only makes the return value false — so log that false if orphaned files would matter, or set 'throw' => true on the disk to get an UnableToDeleteFile naming the path.

More examples

Cleaning up files when a model is deleted

Storing the disk name on the model is what makes this work after somebody migrates uploads from local to S3. The false check is the part people leave out: because a missing file also returns true, the only signal that a bucket policy is silently refusing deletes is that false, and without reporting it the row disappears while the object lives on.

Example · php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;

class Attachment extends Model
{
    protected static function booted(): void
    {
        // 'deleting', not 'deleted': the row is still readable here.
        static::deleting(function (Attachment $attachment) {
            $removed = Storage::disk($attachment->disk)->delete([
                $attachment->path,
                $attachment->thumbnail_path,
            ]);

            if ($removed === false) {
                report(new \RuntimeException("Orphaned file: {$attachment->path}"));
            }

            echo $removed ? 'files removed' : 'delete failed', PHP_EOL;
        });
    }
}

Directories are not files

The first delete returns a value that looks like success while the CSVs are still there, which is exactly the sort of bug that survives review. deleteDirectory removes the folder and its contents recursively. The last block shows the middle ground: list with allFiles, filter in PHP, and pass the resulting array in one delete call — with a sort so the listing order does not vary between drivers.

Example · php
<?php

use Illuminate\Support\Facades\Storage;

Storage::disk('local')->put('exports/2023/q1.csv', 'a');
Storage::disk('local')->put('exports/2023/q2.csv', 'b');

// Does nothing useful - delete only removes files.
var_dump(Storage::disk('local')->delete('exports/2023'));
var_dump(Storage::disk('local')->exists('exports/2023/q1.csv'));   // bool(true)

// This is the method you wanted.
var_dump(Storage::disk('local')->deleteDirectory('exports/2023'));
var_dump(Storage::disk('local')->exists('exports/2023/q1.csv'));   // bool(false)

// Or delete a listed set, which lets you filter first.
Storage::disk('local')->put('exports/2024/q1.csv', 'a');

$old = array_values(array_filter(
    Storage::disk('local')->allFiles('exports'),
    fn ($path) => str_contains($path, '/2024/')
));

sort($old);
print_r($old);

Storage::disk('local')->delete($old);

Discussion

  • Be the first to comment on this lesson.