PHPackages                             muyki-labs/laravel-resumable-jobs - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. muyki-labs/laravel-resumable-jobs

ActiveLibrary[Queues &amp; Workers](/categories/queues)

muyki-labs/laravel-resumable-jobs
=================================

Resumable, checkpointed Laravel queue jobs that survive failures and resume from the last checkpoint, with built-in progress tracking.

v0.1.0(1mo ago)51MITPHPPHP ^8.3CI passing

Since Jun 15Pushed 1mo agoCompare

[ Source](https://github.com/muyki-labs/laravel-resumable-jobs)[ Packagist](https://packagist.org/packages/muyki-labs/laravel-resumable-jobs)[ Docs](https://github.com/muyki-labs/laravel-resumable-jobs)[ RSS](/packages/muyki-labs-laravel-resumable-jobs/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (11)Versions (2)Used By (0)

Laravel Resumable Jobs
======================

[](#laravel-resumable-jobs)

[![Tests](https://github.com/muyki-labs/laravel-resumable-jobs/actions/workflows/run-tests.yml/badge.svg)](https://github.com/muyki-labs/laravel-resumable-jobs/actions/workflows/run-tests.yml)[![PHPStan](https://github.com/muyki-labs/laravel-resumable-jobs/actions/workflows/phpstan.yml/badge.svg)](https://github.com/muyki-labs/laravel-resumable-jobs/actions/workflows/phpstan.yml)[![Latest Version](https://camo.githubusercontent.com/df0dd4ecdcbe78d8c895c0fd33ce1b9486b2efa25d5b8dc524edf3a27429e1d4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d75796b692d6c6162732f6c61726176656c2d726573756d61626c652d6a6f62732e737667)](https://packagist.org/packages/muyki-labs/laravel-resumable-jobs)[![License](https://camo.githubusercontent.com/21604979a3ab1588f4bf01015ccc3103dd53bf0940d52859d5fa17a016456ef3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d75796b692d6c6162732f6c61726176656c2d726573756d61626c652d6a6f62732e737667)](LICENSE.md)

Long-running queue jobs — large imports/exports, video/file processing, multi-step pipelines — start over from scratch when they fail. **Laravel Resumable Jobs** adds a thin, practical checkpoint layer on top of the queue: record progress with `$this->checkpoint('step', ...)`, resume from the last successful checkpoint after a failure, and bind the live progress to a UI.

```
class ImportLargeCsv implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, Resumable;

    public function __construct(public int $importId) {}

    public function checkpointKey(): string
    {
        return "csv-import:{$this->importId}";
    }

    public function handle(): void
    {
        $rows = $this->checkpoint('parse', fn () => $this->parseFile());

        $this->withTotalSteps(count($rows));

        foreach (array_chunk($rows, 500) as $i => $chunk) {
            $this->checkpoint("chunk.$i", fn () => $this->upsert($chunk));
            $this->progress(($i + 1) * 500);
        }

        $this->checkpoint('finalize', fn () => $this->cleanup());
    }
}
```

If this job fails at `chunk.42`, the retry skips `parse` and chunks `0–41` and resumes at `chunk.42`.

Installation
------------

[](#installation)

```
composer require muyki-labs/laravel-resumable-jobs
```

Publish and run the migration (only needed for the default `database` store):

```
php artisan vendor:publish --tag=resumable-jobs-migrations
php artisan migrate
```

Optionally publish the config:

```
php artisan vendor:publish --tag=resumable-jobs-config
```

Requires PHP 8.3+ and Laravel 12 or 13.

Usage
-----

[](#usage)

Add the `Resumable` trait to any queued job. The trait registers a job middleware that loads checkpoint state before `handle()` and clears/marks it on success.

```
use MuykiLabs\ResumableJobs\Concerns\Resumable;

class ProcessVideo implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, Resumable;

    public function handle(): void
    {
        $this->checkpoint('transcode', fn () => $this->transcode());
        $this->checkpoint('thumbnails', fn () => $this->generateThumbnails());
        $this->checkpoint('publish', fn () => $this->publish());
    }
}
```

### Two checkpoint APIs

[](#two-checkpoint-apis)

**Closure form (recommended).** If the step already completed, the closure does not run and its previously stored return value is returned:

```
$rows = $this->checkpoint('parse', fn () => $this->parseFile());
```

**Imperative form.** Records a step as complete and stores a value, without running anything:

```
$this->checkpoint('header', ['columns' => $columns]);

if ($this->completed('header')) {
    $columns = $this->checkpointData('header')['columns'];
}
```

### Progress

[](#progress)

```
$this->withTotalSteps(1000); // declare the total
$this->progress(250);        // 25%
```

Read it anywhere (polling UIs, Livewire, dashboards):

```
use MuykiLabs\ResumableJobs\Facades\ResumableJobs;

$progress = ResumableJobs::progressFor("csv-import:{$importId}");
$progress?->percent(); // 25.0
```

⚠️ Idempotency: read this before shipping
-----------------------------------------

[](#️-idempotency-read-this-before-shipping)

A checkpoint only decides **whether to skip a step**, it does **not** make the step itself atomic. If a step is interrupted halfway (e.g. 600 of 1000 rows written, then the process is killed), the checkpoint for that step was never recorded, so on retry the **entire step runs again**.

That is fine — *as long as each step is idempotent*. Make steps safe to re-run:

- Use `upsert()` / `updateOrCreate()` / `firstOrCreate()` instead of blind `insert()`.
- Keep checkpoints **small and granular** (per chunk), so a re-run repeats at most one chunk.
- Avoid side effects that can't be repeated (e.g. "send email") inside a large step; put them behind their own checkpoint.

```
// Good: re-running this chunk is harmless.
$this->checkpoint("chunk.$i", fn () => User::upsert($chunk, ['email'], ['name']));
```

Choosing the checkpoint key
---------------------------

[](#choosing-the-checkpoint-key)

The checkpoint key must be **stable across retries**. Resolution order:

1. **`checkpointKey()` override (recommended).** Tie it to a business entity, e.g. `"video:{$this->videoId}"`. This is the most robust option and makes the work idempotent even across separate dispatches of the same logical job.
2. **Queued job UUID.** The default. Stable across automatic retries (`$tries`, `backoff`, `release`).
3. **Generated fallback.** Used only for synchronous/standalone execution where no queue UUID exists.

Events
------

[](#events)

Bind to any of these (e.g. to drive a broadcasted progress bar or notifications):

EventFired when`JobStarted`A job runs for the first time (no prior state).`JobResumed`A job runs again with existing checkpoint state.`CheckpointReached`A step completes and is persisted.`CheckpointSkipped`A step is skipped on resume.`ProgressUpdated``progress()` / `withTotalSteps()` is called.`JobCompleted``handle()` finishes successfully.`JobFailedPermanently`The job fails after exhausting its retries.Stores
------

[](#stores)

Configure the store in `config/resumable-jobs.php` via `RESUMABLE_JOBS_STORE`:

- **`database`** (default) — durable and queryable; ideal for progress dashboards.
- **`cache`** — fast, TTL-based (Redis/Memcached); pruning is handled by TTL.
- **`null`** — disables resumability (every run starts fresh).

Register a custom driver:

```
use MuykiLabs\ResumableJobs\CheckpointManager;

app(CheckpointManager::class)->extend('dynamodb', fn ($app) => new DynamoCheckpointStore(...));
```

Concurrency
-----------

[](#concurrency)

To prevent two workers from processing the same key simultaneously, enable locking (requires a cache store with atomic locks):

```
'lock' => ['enabled' => true, 'store' => 'redis', 'ttl' => 600, 'wait' => 0],
```

You can also combine this with Laravel's `WithoutOverlapping` middleware keyed by your `checkpointKey()`.

Commands
--------

[](#commands)

```
php artisan resumable:prune --hours=72   # remove old completed checkpoints
php artisan resumable:status {key}       # inspect a checkpoint
php artisan resumable:clear {key}        # force a fresh restart
```

Enable scheduled pruning in the config under `prune.schedule`.

Retention
---------

[](#retention)

By default completed checkpoints are kept for `retention.completed_hours` (then pruned), and failed checkpoints are kept indefinitely so the job can be resumed. Set `retention.forget_on_completion` to `true` to delete a checkpoint the moment its job succeeds.

Testing
-------

[](#testing)

```
composer test
composer analyse
composer format
```

Contributing
------------

[](#contributing)

Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow, coding standards, and how to run the test suite before opening a pull request.

License
-------

[](#license)

The MIT License (MIT). See [LICENSE.md](LICENSE.md).

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance90

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2cb76096c4c3c1d1daeb127869a13eb06339b1a90c7696e8a463364e4112e6bd?d=identicon)[alihankoc](/maintainers/alihankoc)

---

Top Contributors

[![alihankoc](https://avatars.githubusercontent.com/u/21077340?v=4)](https://github.com/alihankoc "alihankoc (2 commits)")

---

Tags

background-jobscheckpointjobslaravelphpprogressqueueresumablelaravelqueuebatchjobsprogressresumablecheckpointmuyki-labs

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/muyki-labs-laravel-resumable-jobs/health.svg)

```
[![Health](https://phpackages.com/badges/muyki-labs-laravel-resumable-jobs/health.svg)](https://phpackages.com/packages/muyki-labs-laravel-resumable-jobs)
```

###  Alternatives

[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M139](/packages/laravel-pulse)[laravel/horizon

Dashboard and code-driven configuration for Laravel queues.

4.2k95.4M325](/packages/laravel-horizon)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k30.2M151](/packages/laravel-cashier)[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M256](/packages/laravel-ai)[illuminate/queue

The Illuminate Queue package.

20432.6M1.7k](/packages/illuminate-queue)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M348](/packages/psalm-plugin-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
