Django 6.0 (released December 2025) introduced a built-in background tasks framework in django.tasks. For years, running work outside the request-response cycle meant reaching for Celery, Django-Q, or similar third-party libraries. Now Django ships with a standard API for defining and enqueueing tasks, giving the ecosystem a common interface that any backend can implement.
The core idea is simple: you decorate a function with @task, then call .enqueue() to send it to a queue. An external worker process picks it up and executes it. Django handles the API and queuing; the worker infrastructure is pluggable via backends. Two built-in backends cover development and testing, while third-party backends handle production workloads with durable queues and worker management.
Master django background tasks
Take the Django Performance course with hands-on lessons and challenges.
The @task decorator wraps the function so it can only be executed via .enqueue(). Calling email_users() directly will raise an error. The enqueue() call returns a TaskResult that you can use to track the task's progress and retrieve its return value later.
The @task decorator accepts priority (higher runs first), queue_name (for routing to specific workers), and takes_context (injects a TaskContext as the first argument). The .using() method creates a modified copy of the task without changing the original definition, useful for one-off overrides.
The TASKS setting maps backend names to their implementation classes. ImmediateBackend runs tasks synchronously (great for local development). DummyBackend stores tasks without executing them (great for tests — you can inspect default_task_backend.results). Production requires a third-party backend that manages worker processes and durable queues.
TaskResult provides status, return_value, and errors attributes. Call .refresh() (or await .arefresh()) to update the status from the backend. Failed tasks expose TaskError objects with the exception class and a traceback string. The DummyBackend is designed for testing: it collects results without running them, and .clear() resets the state.
When creating database records and enqueueing tasks in the same flow, always use transaction.on_commit() to ensure the worker sees committed data. This is a common pitfall with any task queue. The Celery comparison shows that Django's API is intentionally similar (.enqueue mirrors .delay), but Django's framework is simpler to set up for straightforward background work.
Assuming tasks run immediately after calling `.enqueue()` in production — the task is queued, not executed inline
Tasks are picked up by external worker processes. In development, `ImmediateBackend` runs them synchronously, but this is not the production behavior. Always design your code to handle the delay between enqueueing and execution.
Not configuring a production-ready backend — the built-in `ImmediateBackend` and `DummyBackend` are not suitable for production
Install a third-party backend (like `django-tasks-database`) that provides durable queues and worker processes. The built-in backends are for development and testing only. Without a proper backend, tasks either block the request or never run.
Passing non-JSON-serializable arguments like `datetime` objects, Django model instances, or tuples to `.enqueue()`
Convert arguments to JSON-safe types before enqueueing: use ISO strings for dates (`dt.isoformat()`), primary keys for model instances (`user.id` instead of `user`), and lists instead of tuples. All arguments must survive a `json.dumps()` / `json.loads()` round trip.
Enqueueing tasks inside a database transaction without `transaction.on_commit()` — the worker may execute before the transaction commits
Wrap the `.enqueue()` call with `transaction.on_commit(partial(my_task.enqueue, ...))` so the task is only queued after the database changes are visible. This prevents race conditions where the worker queries data that doesn't exist yet.
Django 6.0 introduced a built-in background tasks framework in `django.tasks` that provides a standard API for defining and enqueueing background work. The `@task` decorator marks functions for background execution, and `.enqueue()` sends them to a queue for processing by external workers. Two built-in backends cover development (`ImmediateBackend`) and testing (`DummyBackend`), while production deployments use third-party backends with durable queues. All task arguments must be JSON-serializable, and tasks should be enqueued with `transaction.on_commit()` when paired with database writes. For simple background work like sending emails or generating reports, this replaces the need for Celery. For complex workflows with chaining, retries, and periodic scheduling, Celery remains the established choice.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.