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

Nova Backup Tool Laravel Package

spatie/nova-backup-tool

A Laravel Nova tool that lists, creates, downloads, and deletes backups, powered by spatie/laravel-backup. Requires Nova and the backup package installed and configured.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you install anything

spatie/nova-backup-tool is a UI layer over spatie/laravel-backup. Nothing works until the underlying package does. So the real first step is:

composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
php artisan backup:run     # must produce a zip somewhere
php artisan backup:list    # must list it

If those two commands don't work from the CLI, the Nova tool has nothing to show and you'll waste an afternoon debugging the wrong layer.

Install the tool

composer require spatie/nova-backup-tool

Register it in app/Providers/NovaServiceProvider.php:

public function tools(): array
{
    return [
        // ...
        new \Spatie\BackupTool\BackupTool(),
    ];
}

A "Backups" menu item appears in the Nova sidebar. That's the whole install — no migrations, no assets to publish (the tool ships compiled JS via Nova's tool discovery).

The three things to check first

  1. config/backup.php — this is where behaviour actually lives: the backup name, the destination disks, what gets included/excluded, the DB connection, and the cleanup strategy. The Nova tool is a thin wrapper around these values; when the UI "doesn't show my disk" or "shows nothing", it's almost always this file.
  2. config/queue.php / QUEUE_CONNECTION — backups are dispatched to a queue. The README explicitly says: any driver except sync.
  3. A running worker — php artisan queue:work. Without it, clicking "Create backup" spins forever.

First use case: a verifiable DB snapshot

The most valuable day-one workflow is not "full backup", it's a fast, verified database-only snapshot:

  1. Go to Backups → Create backup.
  2. Pick the destination disk from the dropdown (populated from backup.destination.disks).
  3. Choose the partial option for database only (--only-db semantics, added in 3.1.0).
  4. Watch the row appear via polling (default: every 1 second).
  5. Download it and open the zip. Confirm there's a db-dump.sql inside and that its size is plausible.

That last step is the one people skip — and it's the only thing that distinguishes "I have backups" from "I have files on S3".


Implementation Patterns

Keep scheduled backups in code, use the tool for ad-hoc

Do not replace your scheduler with the UI. The tool has no scheduler and no retention logic of its own. The pattern that works:

// routes/console.php (Laravel 11+) or app/Console/Kernel.php
Schedule::command('backup:run')->dailyAt('02:00');
Schedule::command('backup:clean')->dailyAt('03:00');
Schedule::command('backup:monitor')->dailyAt('04:00');

Then the Nova tool becomes the human interface: pre-migration snapshots, "grab me a copy of prod from last night", "delete that 40 GB artifact from March". This division of responsibility is exactly what the package is designed for.

Multi-disk strategy

The disk dropdown (added in 5.0.1) exists because real setups use more than one destination. A common production arrangement:

Disk Purpose Notes
backups-local Fast staging / disaster-of-the-day Same host; useless if the host dies
backups-s3 Durable offsite Primary; what you actually restore from
backups-glacier (or a second bucket) Long retention Written by a separate scheduled job, not the UI

Adding a disk to config/filesystems.php is not enough — it must also be listed in config/backup.php:

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

Only those appear in the tool's selector.

Queue isolation

Backups are heavy, long-running jobs and they shouldn't sit behind a thousand queued emails. Register the tool with a dedicated queue (supported since 3.9.0) and run a second worker:

// NovaServiceProvider
public function tools(): array
{
    return [
        new \Spatie\BackupTool\BackupTool(/* queue name */),
    ];
}
php artisan queue:work --queue=backups --timeout=1800 --tries=1

Check the BackupTool constructor signature in your installed version before wiring this up — the queue name has moved around between majors.

Gate it like a loaded gun

A download button on a production DB dump is a data-exfiltration feature for anyone with Nova access. Restrict the tool:

public function tools(): array
{
    $tool = new \Spatie\BackupTool\BackupTool();

    return [
        $tool->canSee(fn ($request) => $request->user()?->hasRole('super-admin') ?? false),
    ];
}

If canSee() isn't available on the tool class in your Nova version, enforce it one layer up — a Gate check in Nova::auth() or a middleware around nova-vendor/backup-tool/* routes. Either way: never ship this visible to all Nova users.

Polling tuning

// config/nova-backup-tool.php
'polling' => true,
'polling_interval' => 1, // seconds

Polling exists to make queued backups feel instant. Sensible per-environment settings:

  • Local/staging: 1 second is fine.
  • Production: raise to 5, or set 'polling' => false entirely if you have several admins leaving the page open. Each poll re-lists the destinations — on S3 that's an API call every second, per open browser tab.
  • Slow buckets (Glacier, Wasabi, on-prem S3): disable polling. The list request itself can take seconds and requests will stack up.

Document the restore path next to the tool

There is no restore button. The tool creates, lists, downloads, deletes — that's the entire surface. Write the runbook and keep it in the repo:

# 1. Download the zip via the Backups UI
# 2. Unzip
unzip 2026-10-19-02-00-00.zip -d restore/
# 3. Database
mysql -h $DB_HOST -u $DB_USER -p $DB_NAME < restore/db-dump.sql
# 4. Files
rsync -av restore/ /var/www/app/storage/app/

Add a link to it in the tool's translated menu label or your internal wiki. The gap between "I can click Create backup" and "I can actually restore" is where incidents go wrong.

Localization

The tool ships translations (Spanish, German, Dutch, French, Persian, Arabic, Portuguese, Lithuanian…) and supports overriding them. To rename "Backups" to match your org's vocabulary ("Snapshots", "Sauvegardes"), publish and edit the tool's lang files, or override the JSON keys for en. Since 3.3.x the menu title follows the app locale.

Pair it with laravel-backup notifications

The Nova tool is not an alerting system. It shows what exists; it does not tell you a backup failed. Configure notifications in config/backup.php (Slack/mail) and backup:monitor against a healthcheck endpoint. The tool's status overview shows the newest backup per disk — useful context, worthless as a pager.


Gotchas and Tips

The sync queue trap

The README's warning is the #1 footgun. With QUEUE_CONNECTION=sync, the backup runs inside the HTTP request: the Nova page hangs, PHP hits max_execution_time, and you get a half-written zip or a 504. Symptom: clicking "Create backup" spins, then nothing appears.

grep QUEUE_CONNECTION .env   # must not be sync
php artisan queue:work       # must actually be running

Also confirm supervisor/systemd is managing the worker — a worker you started by hand dies with your SSH session.

Worker timeout vs. real backup duration

The default queue:work --timeout=60 will kill a database dump of any non-trivial size. The job dies mid-write, the UI polls forever, and failed_jobs holds the evidence. Fix both sides:

  • Worker: --timeout=1800 (and retry_after in config/queue.php must be larger than the timeout, or the job gets executed twice).
  • Job: the tool dispatches a laravel-backup job — check whether it accepts a timeout/$timeout you can override in a subclass if you need it.

Renaming the app silently orphans your history

config('backup.backup.name') (defaults to config('app.name')) prefixes every archive filename. The tool matches files by that pattern. Rename the app or change backup.name in backup.php, and yesterday's backups vanish from the UI — even though they're still sitting on the disk, perfectly restorable. If you must rename, move the existing files to the new prefix (aws s3 mv / rename) in the same change.

Deletion is immediate and permanent

Delete removes the file from the destination disk — no trash, no undo, no versioning unless your bucket has it. Enable S3 versioning or a lifecycle rule you can recover from before you let multiple people drive this UI. There's also no "are you sure this is the last backup?" guard: it's entirely possible to delete your only copy. Changelog 4.1.6 fixed a flashing delete modal, not the underlying finality.

Downloads are streamed through PHP

Clicking download proxies the file through your app server. For multi-gigabyte archives that means:

  • PHP memory_limit and max_execution_time pressure,
  • nginx fastcgi_read_timeout / proxy_read_timeout truncation,
  • a very sad web server.

For anything large, skip the button: aws s3 cp s3://bucket/2026-10-19.zip . or rsync from the on-prem box. Keep the UI for DB-only snapshots (typically hundreds of MB at most).

Container/deploy ephemerality

If your backups land on the local disk of an ephemeral container, every deploy wipes your backup directory — and the Nova list with it. On any containerized setup, the tool should point only at object storage. Check backup.destination.disks for stray local entries in production.

Config caching

Changed config/backup.php and the UI still shows the old disk list? php artisan config:cache wasn't re-run (or was run before your edit). Same class of bug applies to nova-backup-tool's polling config: values are baked into the cached config.

Restart workers after deploy

Workers hold old code in memory. After deploying a new backup.php, a new tool registration, or a package upgrade, run php artisan queue:restart — otherwise the backup job executes against the previous release's configuration and disks.

Debugging order when "Create backup" does nothing

php artisan route:list | grep backup-tool   # routes registered?
php artisan queue:failed                    # job in failed_jobs?
tail -f storage/logs/laravel.log            # dispatch error / missing class
php artisan backup:run --only-db            # does the CLI side even work?

That sequence separates Nova-registration problems, queue problems, and laravel-backup problems — three very different fixes.

Version matching is non-obvious

The tool's major versions track Nova/Laravel support, and the release notes show repeated compat fixes:

Your stack Use
Nova 5 / Laravel 12–13 6.0.4+ (6.0.3 fixed Laravel 13, 6.0.4 corrected it)
Nova 4 / Laravel 9–10 5.0.x
Nova 3 / Laravel 8 4.0.x
laravel-backup 6 2.0+ (1.1.1 "does not work for v6" — explicitly reverted)

Don't chase the latest tag on an old Nova; a mismatched pair usually surfaces as a blank tool page or a Vue asset error in the console rather than a clean Composer failure.

Extension points are limited — plan accordingly

This package is a Vue page plus a handful of controllers calling backup:run / backup:list / delete. There is no service container binding or event to hook. If you need something the UI doesn't do:

  • Restore — write your own Artisan command and a small custom Nova tool that calls it; don't fork this one.
  • Encryption / retention policies — handle in config/backup.php and your bucket lifecycle rules.
  • "Only super-admins can download, everyone can create" — subclass BackupTool and override canSee/canRun, or add a Gate around the download route.

Forking is cheap because the surface is small, but expect to re-merge on every Nova major.

Test the backup, not the button

The tool makes producing backups a one-click affair, which encourages exactly the wrong confidence. Put a quarterly reminder in the runbook: download the latest archive, restore it into a throwaway database, and boot the app against it. The Nova tool will happily keep showing you green rows for backups that are structurally broken.

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
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager