Management Commands & Shell
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Django 6.1 (6.1.1, released 2026-09-02; 6.1.2 is scheduled for 2026-10-06) on Python 3.12 to 3.14, with the 5.2 LTS differences noted where they matter, and was checked on 2026-10-03. The examples ran on Django 6.1.1 and Python 3.13 with SQLite, in a throw-away project with a small catalog app (the Bookshelf catalogue that grows across this section). The django-extensions examples ran with django-extensions 4.1 (2025-04-11), whose PyPI classifiers list Django up to 5.2 but which installed and worked on Django 6.1.1 in that project. Nothing here needs PostgreSQL or Redis.
Every Django project ships with a command-line front end. manage.py creates the project, runs the development server, applies migrations, opens a shell, runs the tests and checks the configuration, and the same mechanism lets your own apps add commands for imports, reports, clean-ups and anything else that must run outside a web request. This page covers the django-admin and manage.py reference and the built-in commands you will use daily, how to write and test a custom command, how the shell command works and how to customise its auto-imports, and the django-extensions package that adds shell_plus and show_urls.
django-admin and manage.py
django-admin is Django’s command-line utility for administrative tasks. manage.py is created in every project by startproject; it does the same thing, but it also sets the DJANGO_SETTINGS_MODULE environment variable so that it points to the project’s settings module and puts the project directory on sys.path. With a single project, use manage.py. With several settings modules, use django-admin together with DJANGO_SETTINGS_MODULE or the --settings option. python -m django is equivalent to django-admin and is handy when the virtual environment’s scripts are not on the PATH.
python manage.py <command> [options]
django-admin <command> [options]
python -m django <command> [options]
python manage.py version
python manage.py help
python manage.py help --commands
python manage.py help unpublish_books
python -m django check --settings=config.settings
Source: django-admin and manage.py.
Running python manage.py help lists every command grouped by the app that provides it, for example [auth], [catalog] or [django]. help <command> prints the command’s description and options. An "app name" argument is the last component of the app’s dotted path: with 'mysite.blog' in INSTALLED_APPS the app name is blog.
Options every command accepts
Besides its own options, every command accepts the following ones.
| Option | Effect |
|---|---|
|
The settings module in Python package syntax, for example |
|
Adds a path to |
|
Shows the full stack trace when a |
|
|
|
Disable colourised output, or force it when it would otherwise be disabled (for example when piping). |
|
Skips the system checks that run before the command. Only available when the command’s |
Colours follow the DJANGO_COLORS environment variable, which selects the dark (default), light or nocolor palette and can override individual roles such as error and success (export DJANGO_COLORS="light;error=yellow/blue,blink;notice=magenta"). Setting DJANGO_COLORS=nocolor is useful in CI logs. The runserver command ignores --verbosity and --traceback.
Source: Default options and Syntax coloring.
The most useful built-in commands
The reference documents some thirty commands. The table groups the ones that matter in daily work; each is documented in the reference page and several have their own page in this section.
| Command | What it does | Notable options |
|---|---|---|
|
Scaffold a project or an app from a template. |
|
|
The development server (a WSGI server with auto-reload; use an ASGI server such as Daphne’s |
|
|
Runs the system check framework. |
|
|
Create and apply schema migrations. |
|
|
List migrations with their applied state, or print the SQL a migration runs. |
|
|
Serialise data to a fixture and load it back. |
|
|
Introspects an existing database and prints model code. |
Treat the output as a starting point, then review fields, keys and |
|
Opens the database’s own command-line client. |
|
|
Prints the settings that differ from Django’s defaults. |
|
|
The interactive interpreter, with auto-imports (see below). |
|
|
Runs the test suite. |
|
|
Account administration (from |
|
|
Collect or locate static files (from |
See the staticfiles documentation. |
|
Deletes expired sessions; run it from cron. |
None. |
|
Sends a test message to confirm email delivery works. |
|
|
Build and extract translation catalogues. |
|
The migration commands in the table are documented on the migrations page of this section, and runserver, startproject and startapp on the getting-started page.
Source: Available commands.
Checking the project: check and check --deploy
check runs Django’s system check framework, and many other commands (runserver, migrate, test) run the checks implicitly first. The tags (models, security, templates, urls, admin, database, …) let you run a subset, and --deploy adds the checks that only make sense in production, such as DEBUG, ALLOWED_HOSTS, SECURE_* and cookie settings.
$ python manage.py check
System check identified no issues (0 silenced).
$ python manage.py check --list-tags
admin
caches
commands
compatibility
database
files
mail
models
security
staticfiles
templates
translation
urls
$ python manage.py check --tag models --tag urls
$ python manage.py check --deploy
?: (security.W018) You should not have DEBUG set to True in deployment.
?: (security.W020) ALLOWED_HOSTS must not be empty in deployment.
...
System check identified 8 issues (0 silenced).
Run against the development settings, --deploy complains about everything the development settings deliberately leave out, so point it at the production settings module (--settings=config.settings.production) or run it in the deployed environment. By default only errors give a non-zero exit status; --fail-level WARNING makes warnings fail too, which is how you make check --deploy a CI gate (in the scratch project that command exits with status 1). Skipping the warnings is a classic mistake in tutorials; treat them as a to-do list.
Source: check, System check framework and Deployment checklist.
Inspecting migrations and data
The commands below ran against the scratch project; they print rather than change anything.
$ python manage.py showmigrations catalog
catalog
[X] 0001_initial
$ python manage.py sqlmigrate catalog 0001
BEGIN;
--
-- Create model Book
--
CREATE TABLE "catalog_book" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(200) NOT NULL, "is_published" bool NOT NULL);
COMMIT;
$ python manage.py makemigrations --check --dry-run
No changes detected
$ python manage.py dumpdata catalog --indent 2
[
{
"model": "catalog.book",
"pk": 1,
"fields": {
"title": "Dune",
"is_published": false
}
},
...
The BEGIN;/COMMIT; wrapper comes from the command’s output_transaction attribute described below. dumpdata output is the fixture format that loaddata reads, so the pair is a quick way to move small data sets between environments or to seed tests.
Source: showmigrations, sqlmigrate and dumpdata.
Creating a superuser without prompts
Interactively, createsuperuser prompts for the password. In scripts and containers, run it non-interactively and pass the password through the environment, never on the command line, where it would end up in shell history and process listings. In non-interactive mode the username field and the required fields also fall back to DJANGO_SUPERUSER_<UPPERCASE_FIELD_NAME> environment variables.
$ DJANGO_SUPERUSER_PASSWORD="$ADMIN_PASSWORD" \
python manage.py createsuperuser --noinput --username admin --email admin@example.com
Superuser created successfully.
Here $ADMIN_PASSWORD stands for a value your deployment injects from its secret store. Without DJANGO_SUPERUSER_PASSWORD the account is created with no usable password and cannot log in until one is set with changepassword.
Source: createsuperuser.
Custom management commands
An app registers its own commands by adding a management/commands/ package. Django registers a command for every module in it whose name does not begin with an underscore, and the module must define a class called Command that extends BaseCommand (or one of its subclasses). The command is available to every project that lists the app in INSTALLED_APPS, so _helpers.py can hold shared code that is not itself a command.
catalog/
__init__.py
models.py
management/
__init__.py
commands/
__init__.py
unpublish_books.py
shell.py
tests.py
A command with arguments
The Bookshelf editors need a command that takes books off the public catalogue, and optionally deletes them. add_arguments() receives an argparse parser, so every argparse feature is available: positional arguments, nargs, type, flags, choices, defaults. Parsed values arrive in the options dictionary of handle().
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
is_published = models.BooleanField(default=True)
def __str__(self) -> str:
return self.title
from django.core.management.base import BaseCommand, CommandError
from catalog.models import Book
class Command(BaseCommand):
help = "Unpublishes (or deletes) the books with the given ids"
def add_arguments(self, parser):
parser.add_argument("book_ids", nargs="+", type=int)
parser.add_argument(
"--delete",
action="store_true",
help="Delete the books instead of unpublishing them",
)
def handle(self, *args, **options):
for book_id in options["book_ids"]:
try:
book = Book.objects.get(pk=book_id)
except Book.DoesNotExist:
raise CommandError(f'Book "{book_id}" does not exist')
if options["delete"]:
book.delete()
verb = "deleted"
else:
book.is_published = False
book.save(update_fields=["is_published"])
verb = "unpublished"
self.stdout.write(
self.style.SUCCESS(f'Successfully {verb} book "{book_id}"')
)
Running it, with the exit status of each call:
$ python manage.py unpublish_books 1 2
Successfully unpublished book "1"
Successfully unpublished book "2"
$ python manage.py unpublish_books 99
CommandError: Book "99" does not exist
$ echo $?
1
$ python manage.py unpublish_books
usage: manage.py unpublish_books [-h] [--delete] [--version] [-v {0,1,2,3}]
...
book_ids [book_ids ...]
manage.py unpublish_books: error: the following arguments are required: book_ids
$ echo $?
2
Three details deserve attention. First, write output through self.stdout and self.stderr instead of print(): these proxies add the trailing newline, honour --no-color and can be redirected, which is what makes the command testable. Second, raise CommandError to report a failure: the command-line runner turns it into a clean message on stderr and exits with status 1 (or the returncode you pass to it), while --traceback shows the stack. Third, handle() may return a string, which Django prints to stdout.
Source: How to create custom django-admin commands (the closepoll example this one adapts).
BaseCommand attributes and methods
BaseCommand is configured through attributes and a few overridable methods; only handle() is mandatory.
| Member | Purpose |
|---|---|
|
Shown by |
|
Adds positional and optional arguments to the parser. |
|
The command’s logic. |
|
Custom message when mandatory positional arguments are missing. |
|
List or tuple of check tags to run first, or |
|
When |
|
When |
|
Option names (for example |
|
Colour helper: |
|
Hooks. Do not call |
Two helpers cover common shapes. AppCommand takes one or more app labels and calls handle_app_config() for each, and LabelCommand takes arbitrary labels and calls handle_label() for each; both replace handle(). If you define init in a command, call super().init(*args, **kwargs).
Commands run with the current active locale. To keep translated text out of data you write (for example into the database), decorate handle() with @no_translations from django.core.management.base. The decorator needs configured settings, so it does not work for commands that run without them.
Source: Command objects.
Overriding commands and standalone scripts
Django registers the built-in commands and then searches INSTALLED_APPS in reverse, and a later discovery with the same name replaces an earlier one. To override a command, give yours the same name and place its app before the overridden command’s app in INSTALLED_APPS. A third-party command you overrode by accident can be reached under a new name by importing its Command class in a differently named module of one of your apps. This is also exactly how the shell override later on this page works.
Management commands are the preferred home for standalone scripts: they get Django set up (settings, apps, ORM) for free, are discoverable through help, and run unchanged from cron or a scheduler. They are not a job queue; see the page on background tasks for django.tasks and Celery.
Source: Overriding commands.
Calling and testing commands with call_command
django.core.management.call_command(name, *args, **options) runs a command from Python. Positional values in *args go through the argument parser, so the command-line style works, including strings such as "--delete". Keyword options skip the parser, so you must pass the right types (verbosity=0, an integer, not "0"), and flags that take no value are passed as booleans (interactive=False, delete=True). Some option names differ from the command line: createsuperuser --noinput becomes interactive=False; the keyword is the option’s dest, found in the command’s source. Options taking several values are passed as a list (exclude=["contenttypes", "auth"]). The return value is whatever handle() returned, and every command accepts stdout and stderr to redirect its output.
from django.core import management
management.call_command("flush", verbosity=0, interactive=False)
management.call_command("dumpdata", "--natural-foreign")
management.call_command("dumpdata", natural_foreign=True)
management.call_command("dumpdata", exclude=["contenttypes", "auth"])
That is the whole testing story: call the command in a test, capture stdout in a StringIO, assert on the database and on the output, and assert that bad input raises CommandError. A TestCase wraps each test in a transaction, so the database changes are rolled back.
from io import StringIO
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase
from catalog.models import Book
class UnpublishBooksTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.book = Book.objects.create(title="Dune")
def test_unpublishes_book(self):
out = StringIO()
call_command("unpublish_books", self.book.pk, stdout=out)
self.book.refresh_from_db()
self.assertFalse(self.book.is_published)
self.assertIn(f'Successfully unpublished book "{self.book.pk}"', out.getvalue())
def test_delete_option(self):
call_command("unpublish_books", self.book.pk, delete=True, stdout=StringIO())
self.assertFalse(Book.objects.filter(pk=self.book.pk).exists())
def test_same_call_in_command_line_style(self):
call_command("unpublish_books", str(self.book.pk), "--delete", stdout=StringIO())
self.assertEqual(Book.objects.count(), 0)
def test_unknown_id_raises_command_error(self):
with self.assertRaisesMessage(CommandError, 'Book "999" does not exist'):
call_command("unpublish_books", 999)
def test_missing_argument_is_a_command_error(self):
with self.assertRaises(CommandError):
call_command("unpublish_books")
$ python manage.py test catalog
Found 6 test(s).
...
Ran 6 tests in 0.006s
OK
Note that, called from code, a failure is not turned into an exit status: CommandError propagates as an exception, which is what the tests above assert. A missing required argument also surfaces as CommandError. If code of yours calls a command, catch the exception where you can handle it.
The shell and its auto-imports
python manage.py shell starts an interactive interpreter with Django configured, so you can try queries, call services and inspect settings against the real database. It picks IPython or bpython if either is installed, and the plain interpreter otherwise; -i python, -i ipython or -i bpython forces one. --no-startup skips the PYTHONSTARTUP and ~/.pythonrc.py script for the plain interpreter, and --command (-c) runs a string or code piped on standard input and exits, which is how scripts and CI use it.
All models from the installed apps are imported automatically (models from apps listed earlier in INSTALLED_APPS win a name clash), together with a few common utilities. At --verbosity 2 the shell lists what it imported; this is the output of the scratch project on Django 6.1.1 with the catalog override shown below:
$ python manage.py shell -v 2 -c "print(reverse('admin:index'))"
15 objects imported automatically:
from django.conf import settings
from django.db import connection, models, reset_queries
from django.db.models import functions
from django.utils import timezone
from catalog.models import Book
from django.contrib.sessions.models import Session
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User, Group, Permission
from django.contrib.admin.models import LogEntry
from django.urls import reverse, resolve
/admin/
$ python manage.py shell --no-imports -c "print('settings' in dir())"
False
$ python manage.py shell -c "print(Book.objects.count(), settings.DEBUG, timezone.now().year)"
3 True 2026
The reference documents connection, reset_queries, models, settings and timezone as the common utilities; the functions import is what 6.1.1 printed in the run above, so treat the exact list as an implementation detail that can grow. --no-imports disables the automatic imports for one session. Those imports run in the interpreter’s namespace, so Book.objects.count() works with no import line at all.
Source: shell.
Customising the auto-imports
To change what the shell imports, override the built-in command: create shell.py in an app’s management/commands/ that subclasses django.core.management.commands.shell.Command, and override get_auto_imports(), which returns a sequence of import paths for the objects or modules to import. Because of the override rule above, an app’s command replaces the built-in one of the same name, and between apps your app must be listed before any other app that ships a shell command.
from django.core.management.commands import shell
class Command(shell.Command):
def get_auto_imports(self):
return super().get_auto_imports() + [
"django.urls.reverse",
"django.urls.resolve",
]
That is why reverse and resolve appear in the verbose listing above. Imports that fail are reported at verbosity 1 or higher, and duplicates are handled for you. To switch the automatic imports off permanently, return None from get_auto_imports().
class Command(shell.Command):
def get_auto_imports(self):
return None
The test in the earlier catalog/tests.py style checks the override without starting a shell:
class ShellTests(TestCase):
def test_auto_imports_include_reverse(self):
from catalog.management.commands.shell import Command
self.assertIn("django.urls.reverse", Command().get_auto_imports())
Source: How to customize the shell command.
django-extensions
django-extensions is a third-party collection of management commands and tools. Install it in the development environment, add it to INSTALLED_APPS (for example from a development-only settings module), and its commands appear under [django_extensions] in help.
$ python -m pip install django-extensions
# config/settings_dev.py
from .settings import * # noqa: F401,F403
INSTALLED_APPS = INSTALLED_APPS + ["django_extensions"]
This ran with django-extensions 4.1 on Django 6.1.1: manage.py check reported no issues and the commands below worked. Its PyPI classifiers stop at Django 5.2, so treat 6.1 support as "worked here, not officially declared" and pin and test it like any other development dependency.
show_urls prints every URL pattern with its view and its name, including nested includes and the admin; it is the quickest answer to "which view serves this path?":
$ python manage.py show_urls | grep -v admin
/books/ catalog.views.book_list catalog:book-list
/books/<int:pk>/ catalog.views.book_detail catalog:book-detail
$ python manage.py show_urls --format table | grep -v admin
URL | Module | Name | Decorator
--------------------+---------------------------+---------------------+----------
/books/ | catalog.views.book_list | catalog:book-list |
/books/<int:pk>/ | catalog.views.book_detail | catalog:book-detail |
shell_plus is an enhanced shell that auto-imports every model and some common helpers, and it can print the SQL of each query, which makes it a convenient place to spot N+1 problems:
$ python manage.py shell_plus --print-sql -c "print(Book.objects.count())"
...
SELECT COUNT(*) AS "__count"
FROM "catalog_book"
Execution time: 0.000040s [Database: default]
3
(The SQL appears syntax-highlighted in a terminal; the colour codes are stripped here.) It also takes --plain, --ipython, --bpython, --notebook and --lab to choose the interface. Since Django 5.2 imports models on its own, shell_plus is now mainly worth it for --print-sql and the extra interface options; see its documentation for its further customisation. The package has many other commands, for example graph_models, generate_secret_key, reset_db and list_model_info (all listed by help in the scratch project); the complete list is in its command reference. Some of them (such as reset_db) are destructive, so keep the package out of production settings. Only show_urls, shell_plus --print-sql --plain and generate_secret_key were run for this page; the other commands are unverified here.
Pitfalls
-
Do not bury business logic inside
handle(). Keep it thin: parse arguments, call a service function, print the result. The same function is then usable from views and tasks, andcall_commandtests stay short. -
A command that writes with
print()cannot be captured bycall_command(…, stdout=…). Useself.stdout. -
A command that must be safe to re-run (cron, deploys) should be idempotent; a half-finished import that crashes on a second run is a common operations headache. Wrap multi-step writes in
transaction.atomic(). -
--verbosityis a number passed inoptions["verbosity"]; use it to gate noisy output instead of inventing a custom--quiet. -
Do not pass passwords or tokens as command-line arguments: they leak into shell history and
ps. Read them from environment variables, ascreatesuperuserdoes. -
check --deployagainst development settings produces noise by design; run it with the production settings. -
A custom command named like a built-in silently replaces it (the same rule that makes the
shelloverride work), sohelpis the place to confirm which app a command comes from.
Where the books are outdated
-
Django 2 by Example builds a custom management command in its later chapters; its example command’s
--daysoption crashed as printed. Build options throughadd_arguments()withargparseas shown above, and test them withcall_command, which the book does not do. -
Books and tutorials from the Django 2 era run
python manage.py shelland import every model by hand, or reach forshell_plusonly to avoid that. Since 5.2 the shell imports models itself, and since 6.0 it imports common utilities such assettings,timezoneandconnectiontoo. -
Several books show
check --deployskipped or ignored (the issue’s errata list names the Django 2 by Example deployment chapter); treat every warning as a to-do, as described above. -
Tutorials that tell you to create the project directory by hand before
startprojectorstartappare out of date: since 6.0 both commands create a missing destination directory. -
Using
manage.pycommands under cron,screenor a hand-rolled loop as a background-job system pre-datesdjango.tasks(6.0) and the production setups later in this section; commands remain the right tool for operations and one-off jobs.
What changed recently
| Version | Change |
|---|---|
5.2 |
The |
6.0 |
The |
6.1 |
|
The 5.2 row is from the 5.2 release notes ("Automatic models import in the shell"); the 6.0 row from the shell and startproject entries of the 6.1 command reference; the 6.1 row from the sendtestemail entry.