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

Checklist Laravel Package

mxms/checklist

mxms/checklist is a Laravel package for building and managing checklists and their items. It offers a simple, model-friendly way to add tasks, mark them complete, and track progress. Use it to organize to-do lists, onboarding steps, or repeatable workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into a Laravel 9/10 app that already runs Nova 4:

composer require mxms/checklist

Before writing any code, open vendor/mxms/checklist/src and vendor/mxms/checklist/dist. The README on this fork is thinner than the original e2consult/novachecklists docs, and the class namespace may still be the upstream one (E2Consult\NovaChecklists\Checklist) rather than Mxms\.... Grep for class Checklist and copy the use statement from there — don't guess it.

1. Add the storage column. A json column is the least surprising choice:

Schema::table('projects', function (Blueprint $table) {
    $table->json('checklist')->nullable();
});

2. Cast it so the field receives a real array instead of a string:

protected $casts = [
    'checklist' => 'array',
];

3. Register the field on a Nova resource. Give every option a stable machine key and a separate human label:

use Mxms\Checklist\Checklist; // verify against vendor/

Checklist::make('Onboarding', 'checklist')
    ->options([
        'contract_signed' => 'Contract signed',
        'kickoff_done'    => 'Kickoff call held',
        'repo_created'    => 'Repository created',
        'billing_set'     => 'Billing configured',
    ]),

First use case: an onboarding/setup checklist on a Project resource, visible on the detail page, where "done" means "the key is present in the stored array". Keep the option list in one place (a config file or a static method on the resource) from day one — you will immediately want to reuse it to compute a progress percentage, and duplicating the list is how these fields rot.

Implementation Patterns

Single source of truth for options + progress. Define the list once, then derive completion on the model:

// config/checklists.php
return ['project' => [
    'contract_signed' => 'Contract signed',
    'kickoff_done'    => 'Kickoff call held',
    'repo_created'    => 'Repository created',
]];

// app/Models/Project.php
public function getChecklistProgressAttribute(): int
{
    $all  = array_keys(config('checklists.project'));
    $done = array_intersect($all, $this->checklist ?? []);

    return $all ? (int) round(count($done) / count($all) * 100) : 0;
}

Then in the resource ->options(config('checklists.project')).

Dynamic options from the database (per-tenant checklists, templates, etc.):

Checklist::make('Checklist')
    ->options(
        ChecklistStep::orderBy('position')->pluck('label', 'key')->all()
    ),

Because only keys are persisted, renaming a step later doesn't corrupt existing records — this is the main reason to prefer DB-driven options over hardcoded labels.

Index column that doesn't dump raw JSON. The default index rendering is the raw stored value, which looks terrible. Either hide it or format it:

Checklist::make('Checklist')
    ->options(config('checklists.project'))
    ->onlyOnDetail();

// or, on the index:
->displayUsing(fn ($value) => count($value ?? []) . ' / ' . count(config('checklists.project')) . ' done')

Programmatic toggling via Actions. Useful for "mark complete" buttons that don't require opening the form:

Action::make('Mark kickoff done')
    ->action(fn (Project $p) => $p->update([
        'checklist' => array_values(array_unique(
            [...($p->checklist ?? []), 'kickoff_done']
        )),
    ])),

Derived status field. Pair the checklist with a read-only badge so you can filter on it:

Text::make('Setup', fn () => $this->checklist_progress . '%')
    ->exceptOnForms()
    ->sortable(), // sorts the real column via a ->sortable(fn ($q) => ...) callback, not the JSON

Read-only review mode. Put the field on detail only, or ->readonly() for roles that should see but not toggle. Wrap writes in your policy — the field has no authorization of its own.

Gotchas and Tips

  • This is a 2-star fork with zero dependents. Pin the exact version ("mxms/checklist": "1.0.0") and commit composer.lock. Plan for the maintainer disappearing: the field is small enough to copy src/ and dist/ into app/Nova/Fields/ and own it. That is a one-afternoon insurance policy.
  • Blank field in the browser = missing compiled assets. Custom Nova fields ship a built dist/js/field.js; small forks routinely ship without it. If the field renders as an empty box, cd vendor/mxms/checklist && npm install && npm run prod (or php artisan nova:publish) before you spend hours debugging your resource.
  • Double-encoded JSON is the number one failure mode. If the column is json/text but the model lacks the array cast, you'll store "[\"a\",\"b\"]" and the checkbox state never round-trips. Column type and cast must agree. If a cast conflicts with other code, decode defensively: is_string($v) ? json_decode($v, true) : $v.
  • null on fresh records. count($model->checklist) throws on an unset column. Always ?? [] or cast with a default.
  • Don't use ->sortable() or ->searchable() on the raw column. MySQL sorts the entire JSON blob lexically, and LIKE '%key%' matches any substring. If you need it, add a real generated column or denormalized progress integer maintained by an observer.
  • Store keys, never labels. Validate incoming values with Rule::in(array_keys($options)) so a removed option can't linger in the column forever.
  • Concurrency is last-write-wins. The whole checklist lives in one column; two admins toggling different boxes simultaneously will clobber each other. Fine for a solo-operator admin panel, not fine for a shared ops team — normalize to a project_checklist_items pivot if that's your use case.
  • Extension point: the field is a standard Nova Field, so ->meta([...]) passes extra config through to the Vue component, and subclassing lets you override component or resolveUsing if you need custom rendering without forking the JS.
  • Testing: assert against the decoded array ($project->fresh()->checklist), not assertDatabaseHas on the raw string — key order and whitespace in the JSON encoding are not guaranteed.
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