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 Backup Manager Laravel Package

sameoldnick/laravel-backup-manager

Laravel package for managing application backups, offering configurable backup routines and storage destinations to help automate database and file backups within Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps backup creation, retention/cleanup and destination (filesystem) management behind services, jobs, artisan commands and optional Inertia/React UI responders. v3.0.0 was a major release (cleanup schedules dispatched as jobs, destination-scoped schedules, moved public APIs); v3.1.1 is a small patch that fixes scheduler environment parsing — pick it up if you run per-environment schedules.

  1. Upgrade the constraint first — composer require sameoldnick/laravel-backup-manager:^3.1. A ^2.0 constraint will otherwise pin you to the old release while you chase confusing class-not-found errors. Staying on ^3.0 still works, but you'll miss the explode() fix for comma-separated scheduler environments.
  2. Publish config and migrations
    php artisan vendor:publish --tag=backup-manager-config --force
    php artisan vendor:publish --tag=backup-manager-migrations --force
    php artisan migrate
    
    The cleanup_schedule_filesystem_configuration pivot migration is required — cleanup schedule destination scoping silently has nowhere to store its selection without it.
  3. Review config/backup-manager.php — the jobs.backup and jobs.cleanup blocks (connection, queue, group, delay, middleware, after-commit) are the first place to look when jobs behave unexpectedly.
  4. Ensure a queue worker — cleanup schedules no longer shell out to backup:clean; they dispatch CleanupJob. If your default queue connection isn't sync, php artisan queue:work (or Horizon) is now mandatory for scheduled cleanups to run at all.
  5. First use case — dispatch a scoped backup from a controller or command:
    // instead of waiting on the artisan command
    PerformBackupService::dispatchBackupJob(disks: ['s3-archive']);
    
    Or dispatch CleanupJob / BackupJob directly onto a specific queue. This is the fastest way to confirm config, destination resolution and queue wiring all work before touching the UI layer.

Where to look first: the published config file, the responder stubs (they encode the new view-data shape), and the React UI guide in the docs which documents selectedDestinationIds.

Implementation Patterns

Queueable operations instead of commands. Anything that used to be Artisan::call('backup:run') or backup:clean inside a controller should now be a dispatch. BackupJob and CleanupJob accept an explicit disks list, which is how you get per-tenant or per-destination backups:

BackupJob::dispatch(disks: [$tenant->disk_name])
    ->onConnection(config('backup-manager.jobs.backup.connection'))
    ->onQueue(config('backup-manager.jobs.backup.queue'));

Prefer PerformBackupService::dispatchBackupJob() when you want the package to apply its own configured queue options rather than hand-chaining them.

Centralise queue configuration, don't scatter it. Put connection/queue/group/delay/middleware/after-commit in config/backup-manager.php and let the scheduler and jobs read from there. Use after-commit where a backup should only be queued once the surrounding DB transaction succeeds (e.g. dispatching from an observer after creating a tenant record).

Destination resolution through one path. Both runners now resolve destinations via the base Runner::createBackupDestinations(), and BackupDestinationsService::getAvailableDestinations() is the single public entry point (the old BackupSchedulesService::getAvailableDestinations() is gone). Build any custom picker or API endpoint on top of that service rather than querying FilesystemConfiguration yourself, so "available" means the same thing everywhere.

Use the collection helpers for filtering instead of hand-written where('is_active', true) chains:

$configurations->getActive();          // active
$configurations->getValid();           // passes validation
$configurations->getActiveAndValid();  // what you usually want for a picker
$configurations->toDiskNames();        // names for the runner

Schedule destination scoping (the headline v3 feature). Cleanup schedules can be attached to specific filesystem configurations. In the store/update request, submit destination_ids:

'destination_ids' => ['array', 'max:255', new SelectableDestinationId()],

Semantics matter:

  • Non-empty array — attaches exactly those destinations; the schedule runs only against their disks.
  • Empty array ([]) — attaches nothing, so the schedule falls back to the app's configured destinations.
  • Key omitted on update — existing selection is left untouched. This is what you want when the user never touched the picker.
  • Attached destination that has since been deactivated — still accepted (the SelectableDestinationId rule allows already-attached ids); a never-attached inactive id is rejected.

Responder / view-data integration. Responder implementations now receive richer view data:

  • renderCreateCleanupSchedule(CreateCleanupScheduleViewData $data) — note the new argument, this is the breaking signature change.
  • EditCleanupScheduleViewData and EditBackupScheduleViewData expose selectedDestinationIds.
  • CreateCleanupScheduleData / UpdateCleanupScheduleData carry destination_ids to your form layer.

Pre-select the picker from $data->selectedDestinationIds, and render the options from $data->configurations. Keep those two concerns separate — see the gotcha below.

Per-environment scheduling. Configure which environments run the scheduler and let the package resolve the list, rather than branching on app()->environment() in your own service provider:

'environments' => 'local, staging, production', // comma-separated, trimmed

Since v3.1.1 this string is explode()d properly with whitespace trimmed and empty segments discarded, so 'production, staging,' and ' production ,staging' both behave as expected.

Factories for tests. If you use BackupFactory::failed() or BackupFileFactory::uploadedFile() with closures, the closures now receive the factory instance, so state-dependent fakes are straightforward.

Gotchas and Tips

  • The destination_ids pre-selection trap. Do not build the edit screen's pre-selection by filtering $data->configurations on is_active. That collection intentionally still contains attached-but-deactivated destinations so they remain visible in the picker. If you filter them out, the next save silently detaches them. Pre-select from selectedDestinationIds; render from configurations.
  • Empty array vs missing key is a real behavioural fork. An empty array resets the schedule to app-configured destinations; omitting the key preserves the current selection. On edit screens, only send destination_ids when the user actually interacted with the picker, otherwise an unrelated cron-expression edit will wipe the destination selection.
  • Comma-separated scheduler environments: bump to ^3.1. Prior to v3.1.1 the environment list was mangled (implode() instead of explode()), so a multi-environment value like 'staging, production' didn't produce the environments you expected — schedules either ran nowhere or in the wrong places. If your scheduler "worked in one env but not others", upgrade before you debug anything else. Empty entries and stray whitespace are now ignored, so trailing commas are safe (but don't rely on them).
  • Silent skipping is now loud-ish, but still check logs. A schedule whose attached destinations are all inactive/invalid no longer falls back to every disk — the run is skipped and logged. If cleanups "stopped happening" after upgrade, this is the first thing to grep for. Earlier 3.x releases also swallowed queue-option errors without stack traces; that's fixed, so re-check logs you may have tuned out.
  • NoValidDisksException is a feature, not a bug. An explicitly empty disks array now throws instead of quietly backing up everything. If you pass a user-supplied disk list that could be empty, guard it before dispatching — the alternative (backing up every disk to every destination) is much worse.
  • Route caching breaking broadcasting auth. Channel authenticators are registered at runtime instead of via route caching. If you had a workaround for /broadcasting/auth returning 403 after php artisan optimize, remove it.
  • BackupFile::file_exists is cached per instance. It now reuses the same storage lookup for meta instead of hitting the disk twice. That's a performance win, but if you delete or upload a file and then read file_exists off an already-hydrated model in the same request, re-fetch the model rather than trusting the cached value.
  • Config cache after upgrade. php artisan config:cache on top of an un-republished config file means jobs.* (and any new scheduler keys) simply don't exist — republish with --force, then php artisan config:clear before debugging anything else.
  • Queue fan-out is bounded at 255 destinations. That's a deliberate guard against one form post triggering hundreds of disk lookups; don't raise it casually on a public form.
  • Static analysis is now first-class. phpstan.neon.dist ships with the package and the analyse/lint composer scripts have memory limits set. If you extend BackupFile, BackupMonitor, FilesystemConfigurationSFTP or the schedule models, mirror the documented @property annotations so your own PHPStan run doesn't regress.
  • Extension point. Override or decorate Runner::createBackupDestinations() (now on the base class, shared by backup and cleanup runners) if destination resolution needs app-specific rules — it keeps both runners consistent, which is exactly what the refactor was for.
  • Dependency pinning. dragonmantank/cron-expression is constrained to ^3.6; if your app pins ^3.3 elsewhere, resolve it before upgrading or Composer will hand you a confusing conflict alongside the package upgrade.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect