Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Storage Copy Laravel Package

sykez/laravel-storage-copy

A small Laravel package for copying files and directories between storage disks, making it easy to move or duplicate content across local, S3, or other configured filesystems with a simple, fluent API.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a thin wrapper: it registers one Artisan command that copies objects between two Laravel Filesystem disks. There is no service class, no facade, and no config file to learn — the command signature is the API. Treat your first ten minutes as reading that signature.

  1. Install as a dev dependency unless you actually run it in production:

    composer require --dev sykez/laravel-storage-copy
    

    Package discovery registers the command automatically on Laravel 5.5+.

  2. Discover the real interface before writing anything else:

    php artisan list | grep -i storage
    php artisan help storage:copy
    

    Note the exact argument names (source/destination path, --disk/--from/--to, recursion flags, overwrite behavior). Every pattern below depends on these flags, so confirm them against the installed version rather than assuming.

  3. Make sure both disks exist in config/filesystems.php and that credentials work independently:

    php artisan tinker
    >>> Storage::disk('local')->put('probe.txt', 'ok');
    >>> Storage::disk('s3')->exists('probe.txt');
    
  4. First use case — the one this package is genuinely good for: move a folder of local uploads into a fresh S3/CDN bucket during a storage migration, then keep the local disk intact as a rollback until you've verified object counts and sample downloads.

Implementation Patterns

Disposable migration, not a sync engine. The happy path is a one-shot copy: source disk is authoritative, destination is empty, you run the command once, verify, then flip FILESYSTEM_DISK in .env. Do the copy while the app is in maintenance mode or with uploads disabled so nothing writes to the source mid-flight.

Seeding environments from a fixture disk. Keep a fixtures disk pointing at tests/fixtures/storage or a shared dev bucket, and copy it into local/public during test bootstrap. This is where the package is least risky — small trees, repeatable, no production data.

Publishing build artifacts. If your pipeline stages assets on one disk (e.g. a build container's local disk) and serves from another (s3 with a CDN in front), wrap the command in your deploy script:

Artisan::call('storage:copy', [
    'source' => 'build/assets',
    'destination' => 'assets/'.config('app.version'),
    '--from' => 'local',
    '--to' => 's3',
]);

Parameter names must match php artisan help output exactly; Artisan::call fails silently-ish on unknown keys.

Scheduling for backup mirroring. Register it as a scheduled command rather than a job:

$schedule->command('storage:copy ...')->dailyAt('02:00')->withoutOverlapping();

withoutOverlapping() matters — a slow copy that outlives its interval will otherwise stack.

Testing the copy. Use Storage::fake() for both disks, Artisan::call() the command, then assert with Storage::disk('dest')->assertExists(). This gives you a regression test on the flag names, which is the part most likely to break across upgrades.

Composition over extension. Since there are no events or hooks, build your own command that loops over disks/prefixes and calls the packaged one repeatedly. That keeps retry/logging/notification logic in your code instead of fighting the package.

Gotchas and Tips

  • Path semantics are the top source of bugs. Confirm whether paths are disk-relative (uploads/2024) or need a leading slash, and whether the destination path is a directory or a target filename. Test with a single file before running on a directory.
  • Visibility is not preserved the way you expect. Files copied to S3 often land private regardless of their source visibility. If you need public objects, check whether the command accepts a visibility option; if not, follow up with Storage::disk('s3')->setVisibility(...) over the copied keys.
  • Memory and streaming. Verify empirically with a 500 MB file whether the command streams or buffers. If it buffers, chunk large trees by subdirectory rather than running one giant copy.
  • Content-Type and metadata. Copies through Laravel's adapter re-guess MIME types from the extension; custom metadata, cache headers, and ACLs set on the originals are generally lost. For CDN assets this means a Cache-Control pass afterwards.
  • No resume, no checksums, no delete-extra. A failure halfway leaves a partial destination. Make every run idempotent (re-run it) and compare object counts before/after. For genuine bidirectional sync, reach for rclone or aws s3 sync — this package is not that.
  • Timeouts. Long copies die at max_execution_time under web-triggered Artisan or at your queue worker's timeout if wrapped in a job. Run from CLI with php -d memory_limit=-1 or split the work.
  • Config cache. After adding a disk, php artisan config:clear locally — a stale cached config is the usual reason a "correctly configured" destination disk isn't found.
  • Pin the version. With 7 stars and zero dependents, there's no ecosystem pressure keeping the command signature stable. Put an explicit constraint in composer.json and re-read help storage:copy after every update.
  • Extension points are effectively nil. If you need filtering, transforms, retries, or per-file logging, write your own command using Storage::disk($from)->allFiles() plus Storage::disk($to)->put() — the package's value is convenience, not capability, and it's roughly fifteen lines to replace.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route