Storage::url

Builds the public URL for a file on a disk whose driver can expose one.

Signature
Storage::url(string $path): string
  • Added in Laravel 5.0

Parameters

ParameterTypeDescription
$$pathstringPath to the file, relative to the disk's configured root.

Return value

stringThe URL for the file. The local driver prepends the disk's url option (/storage by default) and returns a relative URL; S3 returns the fully qualified remote URL.

Errors

RuntimeException with the message "This driver does not support retrieving URLs." when the disk's adapter cannot produce one and no url option is configured.

Changelog

  • PHP 11.xThe local driver gained temporaryUrl support via the 'serve' => true disk option; applications created before that need to add it explicitly.

Storage::url turns a stored path into a URL. What it returns depends entirely on the driver: the local driver prepends the disk's url option — /storage out of the box — and hands back a relative URL; the s3 driver returns the fully qualified bucket URL.

It builds a string, it does not check anything

No request is made and the file's existence is never verified. A URL comes back for a path that was deleted last week, and for a private S3 object it comes back pointing at a resource that will answer AccessDenied. Visibility is set when the file is written (Storage::put($path, $contents, 'public')) or afterwards with setVisibility; url plays no part in it.

What trips people up

On the local driver a URL under /storage only resolves once php artisan storage:link has created the public/storage symlink — per machine, per container, after every fresh deploy that does not preserve it. This is the number one cause of "the image 404s in production but works locally".

The local driver's return value is not URL-encoded, so a stored filename containing a space or a # produces a broken link. Generate your own filenames.

Private files should not use this at all — Storage::temporaryUrl($path, now()->addMinutes(5)) issues a signed, expiring link on the s3 and local drivers instead.

Example

Example · php
<?php

use Illuminate\Support\Facades\Storage;

// Written with public visibility so the URL will actually resolve.
Storage::disk('public')->put('avatars/7.png', 'binary', 'public');

// local driver -> relative, prefixed with the disk's url option.
echo Storage::disk('public')->url('avatars/7.png'), PHP_EOL;
// /storage/avatars/7.png     (needs: php artisan storage:link)

// s3 driver -> fully qualified.
echo Storage::disk('s3')->url('avatars/7.png'), PHP_EOL;
// https://my-bucket.s3.eu-west-1.amazonaws.com/avatars/7.png

// No existence or visibility check is performed.
Storage::disk('public')->delete('avatars/7.png');
echo Storage::disk('public')->url('avatars/7.png'), PHP_EOL;   // same string

// Private files: issue a signed, expiring link instead.
echo Storage::disk('s3')->temporaryUrl('invoices/42.pdf', now()->addMinutes(5)), PHP_EOL;

When to use it

  • Render an <img> tag for a product photo stored on the public disk.
  • Return the CDN URL of an S3 object in an API response after an upload.
  • Build a link to a downloadable brochure that is meant to be world-readable.

Frequently asked questions

Why does my Storage::url link return a 404 on the local disk?

Because the public/storage symlink is missing — run php artisan storage:link on that machine. The symlink is per environment and is not part of your repository, so a fresh container, a new server or a deploy strategy that replaces the public directory all lose it while the files themselves are perfectly fine.

Why does Storage::url throw "This driver does not support retrieving URLs"?

The disk's adapter has no notion of a public URL — sftp and ftp are the usual cases. Either add a 'url' option to that disk's configuration array so Laravel can build one by concatenation, or serve the file through a controller and generate the link yourself.

How do I link to a private file?

Use Storage::temporaryUrl($path, now()->addMinutes(5)), supported by the s3 and local drivers, which returns a signed link that stops working at the expiry you give it. On a local disk created before that support existed, add 'serve' => true to the disk's configuration first.

More examples

Public versus private, chosen at write time

Both url() calls return a well-formed string; only the first one works when fetched. Visibility is decided by the third argument to putFile, not by the URL method, and there is no exception to warn you. For the private object the correct answer is temporaryUrl, which signs the request so it is valid for fifteen minutes and then stops working.

Example · php
<?php

namespace App\Http\Controllers;

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

class FileController extends Controller
{
    public function store(Request $request): array
    {
        // Public: anyone with the link may fetch it, forever.
        $photo = Storage::disk('s3')->putFile('photos', $request->file('photo'), 'public');

        // Private: default visibility, no usable plain URL.
        $invoice = Storage::disk('s3')->putFile('invoices', $request->file('invoice'));

        echo Storage::disk('s3')->url($photo), PHP_EOL;
        // https://bucket.s3.region.amazonaws.com/photos/9f3...c1.jpg  -> 200

        echo Storage::disk('s3')->url($invoice), PHP_EOL;
        // https://bucket.s3.region.amazonaws.com/invoices/2b8...9a.pdf -> AccessDenied

        return [
            'photo'   => Storage::disk('s3')->url($photo),
            'invoice' => Storage::disk('s3')->temporaryUrl($invoice, now()->addMinutes(15)),
        ];
    }
}

When url() throws, and unencoded local paths

The RuntimeException is thrown by the adapter, not by a config validator, so it appears the first time that code path runs in production. Adding a url option to the disk makes url() concatenate it with the path instead. The second half shows the encoding gap the documentation warns about: a space survives as a space and a # is read by the browser as a fragment marker, so the request never asks for the right file.

Example · php
<?php

use Illuminate\Support\Facades\Storage;

// An SFTP or FTP disk has no concept of a public URL.
try {
    echo Storage::disk('sftp')->url('reports/q1.pdf'), PHP_EOL;
} catch (\RuntimeException $e) {
    echo $e->getMessage(), PHP_EOL;
    // This driver does not support retrieving URLs.
}

// Fix by giving the disk a url option in config/filesystems.php:
// 'sftp' => ['driver' => 'sftp', /* ... */, 'url' => 'https://files.example.com'],

// The local driver does NOT url-encode what it returns.
Storage::disk('public')->put('docs/Q1 report #2.pdf', 'x');

echo Storage::disk('public')->url('docs/Q1 report #2.pdf'), PHP_EOL;
// /storage/docs/Q1 report #2.pdf   <- the # truncates the link in a browser

// So generate safe names yourself.
$name = 'docs/'.\Illuminate\Support\Str::slug('Q1 report #2').'.pdf';

echo $name, PHP_EOL;   // docs/q1-report-2.pdf

Discussion

  • Be the first to comment on this lesson.