charleslightjarvis/laravel-todo
A Laravel package for adding todo lists and task management to your application. It offers a simple way to create, track, and organize to-dos, with models, migrations, and a clean API that fits naturally into Laravel projects.
Install the package and publish its migration before writing any code:
composer require charleslightjarvis/laravel-todo
php artisan vendor:publish --tag="todo-migrations"
php artisan migrate
Optionally publish the config if you need to change defaults:
php artisan vendor:publish --tag="todo-config"
Start by reading three things, in order:
config/todo.php to see what is actually configurable.Add the HasTodos trait to the first model that should own todos:
use CharlesLightJarvis\LaravelTodo\Traits\HasTodos;
class Project extends Model
{
use HasTodos;
}
The quickest first use case is attaching a todo to a model and querying it back:
$project->todos()->create([
'title' => 'Fix checkout bug',
'priority' => 'high',
]);
$pending = $project->todos()->pending()->get();
Use the Todo facade when you want to work contextually against a model without going through the relation:
use Todo;
Todo::for($project)->create(['title' => 'Update dependencies']);
Todo::for($team)->overdue()->get();
Trait-first for model-owned workflows. Prefer $model->todos() when the todo belongs to the page/model you are already working with. It keeps controllers and actions explicit and avoids resolving the facade for simple CRUD.
$user->todos()->create([
'title' => 'Review PR',
'priority' => 'medium',
'status' => 'pending',
]);
Facade for cross-model dashboards and contextual queries. The Todo::for($model) API is useful when the owning model is polymorphic or comes from a route binding:
Route::get('/projects/{project}/todos', function (Project $project) {
return Todo::for($project)
->pending()
->highPriority()
->get();
});
Compose built-in scopes instead of writing local filters. The package ships pending(), completed(), overdue(), highPriority(), and dueToday(). Chain them for common views:
$user->todos()->pending()->dueToday()->get();
$user->todos()->overdue()->highPriority()->get();
Todo::for($team)->completed()->get();
Model the status workflow explicitly. Supported statuses are pending, in_progress, completed, and cancelled. Keep transitions in one place rather than scattering string updates:
$todo->update(['status' => 'in_progress']);
$user->completeTodo($todo);
Use priority for triage views. Priority values are low, medium, and high. Combine priority with due-date scopes for “what needs attention today” widgets:
$urgent = $user->todos()
->pending()
->highPriority()
->dueToday()
->get();
Handle creator tracking deliberately. Todos have a polymorphic creator relation. Do not assume the authenticated user is attached automatically. Set the creator where the todo is created, using the relation API documented in the README, and eager load it in list views:
$todos = $user->todos()->with('creator')->pending()->get();
Use eager loading and counts for indexes. Todo lists commonly show owner, creator, and counts. Avoid N+1 queries:
$projects = Project::withCount([
'todos as pending_todos_count' => fn ($q) => $q->pending(),
])->get();
Keep validation at the application boundary. Because status and priority are string values in v1, validate them with Rule::in([...]) in form requests rather than relying on the package to reject invalid values.
Test with the real relationship. Use RefreshDatabase and assert against the trait/facade API so package upgrades surface behavioural changes:
it('completes a todo', function () {
$user = User::factory()->create();
$todo = $user->todos()->create(['title' => 'Test']);
$user->completeTodo($todo);
expect($todo->fresh()->status)->toBe('completed');
});
vendor:publish --tag="todo-migrations" as a required step. If the table is missing, check that this ran and that the migration is in database/migrations.config/todo.php, include php artisan config:cache in deployment. If you change table names or model bindings later, clear cached config before debugging “wrong table” errors.completeTodo() is idempotent. Check the implementation before calling it from retries, jobs, or UI double-submits. Guard with a status check where completion triggers side effects.TodoCreated, TodoCompleted, and TodoCancelled are listed as “Coming Soon.” Do not design listeners around them in v1; put side effects in your service/action layer for now.todos:prune artisan command is also “Coming Soon.” Plan your own cleanup for cancelled or old completed todos.overdue() and dueToday() will return nothing useful. Make due dates optional but intentional in your forms.Relation::morphMap(), confirm how the package stores todos.todoable_type and creator_type. Changing morph maps after data exists can orphan relationships.creator to avoid one query per row.Todo::for() fails with a class-not-found or alias error, check config/app.php aliases and whether package discovery is enabled in your environment.Todo::with(['todoable', 'creator'])->pending()->get();
vendor/.How can I help you explore Laravel packages today?