Storage::put

Writes contents to a path on a disk, accepting a string, a stream or an uploaded file.

Signature
Storage::put(string $path, StreamInterface|File|UploadedFile|string|resource $contents, mixed $options = []): string|bool
  • Added in Laravel 5.0

Parameters

ParameterTypeDescription
$$pathstringDestination path relative to the disk's configured root. Missing directories are created.
$$contentsStreamInterface|File|UploadedFile|string|resourceThe data to write. A resource or PSR-7 stream is written with Flysystem's streaming support instead of being loaded into memory.
$$optionsoptionalmixed= []An options array such as ['visibility' => 'public'], or the visibility as a bare string.

Return value

string|booltrue on a successful write, false when it fails. When $contents is a File or UploadedFile the method delegates to putFile and returns the generated path string instead.

Errors

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

Changelog

  • PHP 11.xThe local disk's root moved to storage/app/private, so an unqualified put lands in a different directory than it did on Laravel 10.

Storage::put writes a file. The path is always relative to the disk's configured root, and that relativity is the sandbox: a local disk rooted at storage/app/private cannot be talked into writing outside it, because Flysystem normalises the path and rejects traversal.

What you can pass as contents

A string is the common case. A PHP resource or a PSR-7 stream is written through Flysystem's stream support, which keeps memory flat for large files — important when the alternative is file_get_contents on a 2 GB upload. An Illuminate\Http\File or UploadedFile is handled by putFile instead, so the return value becomes the generated path rather than a boolean.

What trips people up

The return type is not stable. Compare with === false rather than truthiness, and remember that passing an uploaded file gives you a string back.

Failures are silent by default: a full disk, a permissions problem or a bad S3 credential returns false and nothing is logged. Either check the return value on every write or set 'throw' => true on the disk so Flysystem raises UnableToWriteFile.

Visibility must be requested. A file written to S3 without 'public' is private, and the URL you generate for it returns AccessDenied. Directories in the path are created for you.

Example

Example · php
<?php

use Illuminate\Support\Facades\Storage;

// String contents. Missing directories are created.
$ok = Storage::disk('local')->put('exports/2024-05/report.csv', "id,total\n1,99\n");

var_dump($ok);           // bool(true)

// ALWAYS check: a failure is silent unless the disk sets 'throw' => true.
if (Storage::disk('s3')->put('exports/report.csv', 'id,total') === false) {
    echo 'write failed', PHP_EOL;
}

// Visibility as a bare string, or inside an options array.
Storage::disk('s3')->put('images/logo.png', $png = 'binary', 'public');
Storage::disk('s3')->put('images/logo.png', $png, ['visibility' => 'public']);

// A resource streams instead of buffering the whole file in memory.
$handle = fopen('/tmp/big.zip', 'rb');
Storage::disk('s3')->put('archives/big.zip', $handle);
fclose($handle);

echo Storage::disk('local')->get('exports/2024-05/report.csv'), PHP_EOL;

When to use it

  • Persist a generated CSV export to S3 from a queued job and store the returned path.
  • Stream a large upload straight to storage without loading it into memory.
  • Write a public product image with public visibility so its URL works for anonymous visitors.

Frequently asked questions

Why does Storage::put return false?

The write failed — a full volume, wrong permissions on the root directory, an invalid S3 credential, or a bucket policy denying the operation. Nothing is thrown or logged by default; set 'throw' => true in the disk's config array to get a League\Flysystem\UnableToWriteFile with the real reason instead of a bare false.

Do I need to create the directory before writing to it?

No, put creates any missing directories in the path. On the local driver they are created with the disk's configured directory permissions, 0755 for public visibility and 0700 for private.

How do I write a large file without exhausting memory?

Pass a PHP resource from fopen, or use putFile with an Illuminate\Http\File instance, and Flysystem streams the contents through. Reading the file into a string first defeats this and will hit memory_limit on anything sizeable.

More examples

put returns a string when you hand it an uploaded file

The same method returns bool(true) in one call and a path string in the next, purely because of the argument type. Code that writes if (Storage::put(...) === true) breaks the moment somebody passes an uploaded file. Prefer putFile and putFileAs when the input is a file — the name says what comes back, and putFile streams rather than buffering.

Example · php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class AvatarController extends Controller
{
    public function store(Request $request): array
    {
        // String contents -> bool.
        $wrote = Storage::disk('public')->put('avatars/readme.txt', 'avatars live here');
        var_dump($wrote);                      // bool(true)

        // UploadedFile -> delegates to putFile, returns the generated path.
        $path = Storage::disk('public')->put('avatars', $request->file('avatar'));
        echo $path, PHP_EOL;                   // avatars/8f2c...c1.jpg

        // Same thing, said explicitly:
        $path = Storage::disk('public')->putFile('avatars', $request->file('avatar'));

        // With a filename you choose:
        $path = Storage::disk('public')->putFileAs('avatars', $request->file('avatar'), $request->user()->id.'.jpg');

        return ['path' => $path];
    }
}

Paths are confined to the disk root

Every path is resolved under the disk's root, so a leading slash means nothing and ../ is refused rather than escaping. That containment is the security property the disk abstraction gives you over raw file_put_contents. It does not sanitise names, though — a user-supplied filename can still collide with an existing file or produce a path that breaks URL generation, so generate names yourself.

Example · php
<?php

use Illuminate\Support\Facades\Storage;

// Disk root: storage/app/private
Storage::disk('local')->put('reports/q1.txt', 'ok');
echo Storage::disk('local')->path('reports/q1.txt'), PHP_EOL;
// /var/www/app/storage/app/private/reports/q1.txt

// A leading slash is not absolute - it is still relative to the root.
Storage::disk('local')->put('/reports/q2.txt', 'ok');
echo Storage::disk('local')->path('reports/q2.txt'), PHP_EOL;
// same directory as above

// Traversal is rejected by Flysystem's path normalisation.
try {
    Storage::disk('local')->put('../../.env', 'PWNED');
} catch (\League\Flysystem\FilesystemException $e) {
    echo 'refused: '.get_class($e), PHP_EOL;
}

// Which is why user input must still never build the path directly:
$safe = 'uploads/'.bin2hex(random_bytes(8)).'.dat';
var_dump(Storage::disk('local')->put($safe, 'user data'));

Discussion

  • Be the first to comment on this lesson.