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

Tomato Backup Laravel Package

tomatophp/tomato-backup

Tomato Backup brings backup management to TomatoPHP and Filament admin panels. Create, schedule, and restore database and file backups from a simple interface, with support for local and cloud storage via Laravel's filesystem. Integrates with queues for automated background backups.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the engine first, the plugin second. The plugin is a presentation layer; almost everything that can fail lives in spatie/laravel-backup. Install both, then publish both configs:

composer require tomatophp/tomato-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"   # this is the real config
php artisan vendor:publish --tag=tomato-backup-config                        # verify the tag in the provider

Before touching the panel, open config/backup.php and read it top to bottom. That file — not the plugin — is where destination disks, retention, encryption, queue, and monitoring thresholds live. It is the only file you will edit in a normal week.

Register the Filament plugin on the TomatoPHP stack:

// app/Providers/Filament/AdminPanelProvider.php
->plugin(\TomatoPHP\TomatoBackup\TomatoBackupPlugin::make())

Confirm the exact class name and constructor signature against vendor/tomatophp/tomato-backup/src/. The package is small enough to read in full before you commit to it — do that, because there is no release since April 2024 and no community to ask.

Create the destination disk. Add a disk in config/filesystems.php, then point backup.destination.disks at it. Verify the disk is writable before the first run — a misconfigured disk fails after the archive is built, wasting minutes and giving false confidence.

Prove the engine, not the UI:

php artisan backup:run --only-db
php artisan backup:list

Then download that archive and restore it into a scratch database by hand. The day you can do that, backup exists as a capability. The day the panel shows a green checkmark, nothing has been proven.

Where to look, in order: config/backup.php → the package's service provider (does it read config or a settings table?) → the resource/page classes → the plugin class. The second item decides whether you have one source of truth or two.

Implementation Patterns

Daily loop: panel for humans, artisan for you. Operators click a button in the panel; your runbook says php artisan backup:run. When a Filament upgrade breaks the page, the engine still works and nobody's incident response depends on a broken view.

Split the schedule by content, not by convenience. Databases and files have different sizes, cadences, and blast radii:

Schedule::command('backup:run --only-db')->dailyAt('02:00')->onOneServer();
Schedule::command('backup:run --only-files')->weeklyOn(0, '03:00')->onOneServer();
Schedule::command('backup:clean')->dailyAt('04:00')->onOneServer();
Schedule::command('backup:monitor')->dailyAt('05:00');

Give backups their own queue. Set backup.queue to a dedicated name and run a worker for it. Any archive of nontrivial size will occupy a worker for minutes; sharing the default queue means real user jobs stall behind a dump.

Alert on backup age, not job exit status. The dangerous failure is the scheduler silently dying while backup:clean prunes the last good archive. backup:monitor with monitoring_backup_older_than_days set to roughly twice your cadence — not 30 — plus a notification channel a human actually reads, is the only thing that catches it.

Two destinations for anything you care about. Spatie accepts an array of disks; local plus an S3-compatible bucket costs almost nothing and survives the classic single-volume failure:

'destination' => ['disks' => ['local', 's3-backups']],

If the plugin writes settings to a database table, treat it as operator convenience only. Keep destination, retention, and encryption in version-controlled config so a policy change is a reviewed PR. Otherwise a panel edit drifts from what the repo claims, and nobody notices until an audit or a restore.

Wire a tiny action if you're not on Filament. The entire value of the wrapper is one call:

Artisan::call('backup:run', ['--only-db' => true]);

You can put that behind a Filament action, a Nova action, or a signed route in an afternoon. Reproducing it means the dependency is optional, which changes how you argue about upgrade risk.

Treat restore as a procedure with a script, not a button. Fetch the archive, decrypt and unzip, mysql < db-dump.sql or pg_restore, rsync the files payload. Store that script next to the app, rehearse it quarterly against a staging DB, and log the rehearsal result. Whatever restore affordance the panel offers is unproven until you have watched it succeed on a copy — and Spatie's engine notably does not ship a restore command.

Debugging an empty panel starts outside the panel. Confirm schedule:run is actually invoked by cron (php artisan schedule:list to read the registered entries) and that a worker is consuming backup.queue. An empty backup table renders identically whether the scheduler is dead or just nothing has been scheduled.

Gotchas and Tips

APP_KEY is your backup encryption key. With Spatie's default encryption: 'default', archives are encrypted with the application key. Rotate APP_KEY and every prior archive becomes unreadable — and you find out during a restore. Either set an explicit backup.password held in a secrets manager, or treat key rotation as an event that requires re-encrypting archives first.

Check the pinned engine constraint before adoption. The last release is 2024-04-29, in the Laravel 10/11 and Filament 3 era. If the plugin's composer.json pins spatie/laravel-backup to an older minor, composer require may silently constrain or downgrade the engine you actually want. A thin wrapper that drags a stale engine is worse than no wrapper. Verify in a throwaway branch first.

Read the source before you promise "restore from any point." The description advertises restore; the underlying engine has no restore command. Whatever the panel exposes is likely a download link or a narrow DB-import helper. Confirm before it enters a conversation with anyone who owns an SLO.

Retention plus a quiet failure is the highest-severity mode you can ship. backup:clean deletes on schedule regardless of whether recent runs succeeded. Build age-based alerting before you enable aggressive retention, in that order.

mysqldump on a live primary holds locks. This is engine behavior the plugin neither causes nor cures. For anything with traffic, point the dump at a replica or accept a write-stall window. Don't let an admin panel imply the package solves scale.

S3 needs league/flysystem-aws-s3-v3 and a correct root. The failure mode is an upload error long after the archive is finished — minutes wasted per attempt, and if nothing alerts, a false sense of coverage. Test the disk with a small file first.

Raise memory_limit and max_execution_time on the backup worker specifically, not globally. Large file payloads will exceed sane defaults and you don't want to loosen limits for web requests to fix it.

Fork it into your namespace on day one. MIT, zero stars, zero dependents, no release in over a year. A repositories VCS entry pointing at your fork costs twenty minutes and means the next Laravel or Filament major is an afternoon of patching rather than a blocked upgrade. The wrapper is thin enough that this is realistic — but only because you already read it.

Watch for a second source of truth. If the service provider reads settings from a table instead of config/backup.php, you now have two policies. Keep a seed/migration that rehydrates the table from config, or disable the settings page entirely and let config win.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core