Laravel Read-Through Filesystem: Lazy Storage Migration | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel Read-Through Filesystem: Lazy Storage Migration Between Buckets        On this page       1. [  Laravel Read-Through Filesystem: Lazy Storage Migration ](#laravel-read-through-filesystem-lazy-storage-migration)
2. [  Configuring a Read-Through Disk ](#configuring-a-read-through-disk)
3. [  How Reads, Writes, and Deletes Are Routed ](#how-reads-writes-and-deletes-are-routed)
4. [  Sharp Edges to Know ](#sharp-edges-to-know)
5. [  Reading Without Promoting ](#reading-without-promoting)
6. [  A Four-Step Migration Playbook ](#a-four-step-migration-playbook)
7. [  Key Takeaways ](#key-takeaways)

  ![Laravel Read-Through Filesystem: Lazy Storage Migration Between Buckets](https://cdn.msaied.com/569/7759742f96ca5582353a70049ec950e1.png)

 [  Laravel ](https://msaied.com/articles?category=laravel)  #Laravel   #Filesystem   #Storage Migration   #S3   #Laravel 13  

 Laravel Read-Through Filesystem: Lazy Storage Migration Between Buckets 
=========================================================================

     18 Aug 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Laravel Read-Through Filesystem: Lazy Storage Migration  ](#laravel-read-through-filesystem-lazy-storage-migration)
2. [  02   Configuring a Read-Through Disk  ](#configuring-a-read-through-disk)
3. [  03   How Reads, Writes, and Deletes Are Routed  ](#how-reads-writes-and-deletes-are-routed)
4. [  04   Sharp Edges to Know  ](#sharp-edges-to-know)
5. [  05   Reading Without Promoting  ](#reading-without-promoting)
6. [  06   A Four-Step Migration Playbook  ](#a-four-step-migration-playbook)
7. [  07   Key Takeaways  ](#key-takeaways)

 Laravel Read-Through Filesystem: Lazy Storage Migration
-------------------------------------------------------

Moving millions of files between storage buckets is expensive and slow. A bulk `aws s3 sync` copies every stale file, costs real money in egress, and takes days. Scattering `Storage::disk('old')` fallbacks through your codebase is the other common approach — and it never quite gets cleaned up.

Laravel 13.26 solves this with a first-class `read-through` filesystem driver. You compose it from a primary disk and a fallback disk, and the driver handles the two-disk dance internally. Application code never knows two buckets exist.

Configuring a Read-Through Disk
-------------------------------

Add the driver to `config/filesystems.php` and reference your existing disks by name:

```php
'disks' => [
    'r2' => [
        'driver' => 's3',
        // Cloudflare R2 credentials...
    ],
    'legacy-s3' => [
        'driver' => 's3',
        // the bucket you are leaving...
    ],
    'assets' => [
        'driver' => 'read-through',
        'primary' => 'r2',
        'fallback' => 'legacy-s3',
    ],
],

```

Controllers and jobs call `Storage::disk('assets')` as normal. Both `primary` and `fallback` also accept an inline config array if you prefer not to register the underlying disks separately. The manager validates the pair at resolution time — a missing side, duplicate disks, or a self-referencing disk throws an `InvalidArgumentException` immediately.

How Reads, Writes, and Deletes Are Routed
-----------------------------------------

Understanding the routing rules before going to production matters:

- **Reads** (`get()`, `readStream()`) check the primary first, then the fallback. A fallback hit copies the file to the primary and returns the contents. Streamed reads buffer through `php://temp` to avoid loading large files into memory.
- **Writes, deletes, moves, and copies** target the primary only.
- **Directory listings** (`files()`) reflect the primary only — unpromoted fallback content is invisible to directory iteration.
- **Existence checks and metadata** (`exists()`, `size()`, `mimeType()`, `lastModified()`, `url()`, `temporaryUrl()`) query whichever disk holds the file without triggering a copy.

### Sharp Edges to Know

Two behaviours can surprise you. First, `files()` only lists primary content, so anything that iterates a directory to discover files will miss unpromoted objects. Second, `delete()` only removes the file from the primary. If the file still exists on the fallback, the next read will resurrect it. During a migration this is usually fine — the fallback is going away — but it is worth knowing.

Promotion is best-effort by default: if copying to the primary fails, the read still succeeds from the fallback and the exception is swallowed. Set `'throw_on_promotion_failure' => true` to surface those errors immediately.

Reading Without Promoting
-------------------------

Sometimes you want the layered reads without the automatic migration. Pass `'copy' => false` to disable promotion:

```php
'assets' => [
    'driver' => 'read-through',
    'primary' => 'local-assets',
    'fallback' => 'production-s3',
    'copy' => false,
],

```

This is ideal for local development seeded from a production database snapshot: file references in the database resolve against the production bucket without slowly mirroring gigabytes onto your laptop. It also works as a cautious first phase of a real migration — cut reads over and watch error rates before allowing promotion to write to the new bucket.

A Four-Step Migration Playbook
------------------------------

1. Create the new bucket and add its disk config alongside the old one.
2. Repoint your existing disk name at a read-through pair (new bucket primary, old bucket fallback). New uploads land in the new bucket immediately; every requested file promotes itself on first read.
3. After normal access patterns have cycled, backfill the remaining long tail with a one-off sync or a batched background job — you only move what nobody asked for.
4. Swap the read-through config for a plain disk pointing at the new bucket and decommission the old one.

At no point does a single deploy flip all traffic at once, and rolling back is a config change because the old bucket remains complete throughout.

Key Takeaways
-------------

- The `read-through` driver shipped in Laravel 13.26 (PR #61140).
- Hot files migrate themselves on first access; cold files stay put until you decide.
- Writes, deletes, and directory listings always target the primary only.
- Set `throw_on_promotion_failure => true` to surface copy failures instead of swallowing them.
- Use `copy => false` for read-only layering in development or cautious migration phases.
- Rolling back is a config change — the fallback bucket stays intact throughout.

---

*Source: [Laravel Read-Through Filesystem: Lazy Storage Migration](https://laravel-news.com/laravel-read-through-filesystem) — Laravel News, August 19, 2026.*

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-read-through-filesystem-lazy-storage-migration-between-buckets&text=Laravel+Read-Through+Filesystem%3A+Lazy+Storage+Migration+Between+Buckets) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-read-through-filesystem-lazy-storage-migration-between-buckets) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  What does the Laravel read-through filesystem driver do?        It composes two disks — a primary and a fallback — into a single named disk. Reads check the primary first; on a miss, the file is served from the fallback and automatically copied to the primary. Writes and deletes always target the primary only. 

      Q02  What happens if promotion to the primary disk fails during a read?        By default, the exception is swallowed and the read still succeeds from the fallback. Set 'throw_on_promotion_failure' =&gt; true in the disk config to surface the error immediately instead. 

      Q03  Can I use the read-through driver without migrating files — for example in local development?        Yes. Set 'copy' =&gt; false in the disk config. Fallback hits are served directly and nothing is promoted to the primary. This is useful for local environments seeded from a production database where files only exist in the production bucket. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Read-Through Disks and Debounced Listeners in Laravel 13.26](https://cdn.msaied.com/568/580ae69765054f5f750614e4d977ff56.png) Laravel 13.26 Filesystem Queue 

### Read-Through Disks and Debounced Listeners in Laravel 13.26

Laravel 13.26 ships a read-through filesystem driver for lazy storage migration, extends #\[DebounceFor\] to que...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 18 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/read-through-disks-and-debounced-listeners-in-laravel-1326) [ ![Object Storage Migrations with Laravel's Read-Through Filesystem](https://cdn.msaied.com/565/f830d15d4a1287d381fa05e631ea2aba.png) Laravel 13 Object Storage S3 

### Object Storage Migrations with Laravel's Read-Through Filesystem

Laravel 13 introduces a read-through filesystem driver that lets you migrate from S3 to R2 without downtime. N...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 18 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/object-storage-migrations-with-laravels-read-through-filesystem) [ ![Livewire v4.4.1 Released: Bug Fixes, Alpine 3.16.2, and Laravel 13 Compatibility](https://cdn.msaied.com/564/c64b65959ad8ade491c78f3482f22996.png) Livewire Laravel Alpine.js 

### Livewire v4.4.1 Released: Bug Fixes, Alpine 3.16.2, and Laravel 13 Compatibility

Livewire v4.4.1 ships 16 fixes and improvements including Alpine.js bumped to 3.16.2, cached computed property...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 18 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/livewire-v441-released-bug-fixes-alpine-3162-and-laravel-13-compatibility) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
