skip to content

In Django, what are AppConfig.name and AppConfig.label, and why is changing an app's label after migrations have run risky?

level: seniorimportance: nice to knowfreq 26%

answer

  1. full dotted path versus short name
  2. last component by default
  3. must be unique per project
  4. tables, migrations, content types

basics

~20 s

AppConfig.name is the app's full dotted Python path; AppConfig.label is its short identifier, defaulting to the last path component. The label is baked into default table names, migrations, content types and permissions, so changing it later breaks databases.

solid answer

~40 s

`name` is the full Python path, such as `"clinic.patients"`, and tells Django which package the config applies to. `label` is the short identifier, defaulting to the last component (`"patients"`), and is what Django uses in `app_label`. Both must be unique across the project: installing `django.contrib.auth` and a local `accounts.auth` fails with `ImproperlyConfigured: Application labels aren't unique, duplicates: auth` unless one config sets a different `label`. The label is persistent: default table names are `<label>_<model>`, migration files reference `("patients", "0003_...")`, content types store `app_label`, and permission strings read `patients.add_patient`. Django's docs warn that changing it after migrations have been applied is a breaking change — for a reusable app, for every existing install.

go deeper

for a junior

Know that name is the full dotted path and label the short name, which defaults to the last part of the path.

for a middle

Explain the uniqueness rule, recognise 'Application labels aren't unique' and fix it with a custom AppConfig label.

for a senior

List where the label is persisted, tables, migrations, content types, permissions, and treat changing it as a data migration, not a rename.

for a principal

Treat app labels as long-lived identifiers in a naming policy, so generic names are avoided before the first migration ships.

## Two identifiers for one app Every installed app has an `AppConfig`, and two of its attributes identify it: | Attribute | Example | Default | Used for | |---|---|---|---| | `name` | `"clinic.patients"` | none; must be set | locating the package to import | | `label` | `"patients"` | last component of `name` | `app_label` everywhere in the ORM and contrib apps | | `verbose_name` | `"Patients"` | `label.title()` | human-readable display, for example in the admin | The docs require both `name` and `label` to be unique across a project, and the label to be a valid Python identifier. ## When labels clash On a clinic-booking monolith, a team creates `clinic.auth` for clinic-specific sign-in logic next to `django.contrib.auth`. Both default to the label `auth`, so startup fails: ```text django.core.exceptions.ImproperlyConfigured: Application labels aren't unique, duplicates: auth ``` The fix is a custom config that relabels one app: ```python # clinic/auth/apps.py from django.apps import AppConfig class ClinicAuthConfig(AppConfig): name = "clinic.auth" label = "clinic_auth" ``` The settings reference notes you cannot install the same package twice at all: names must be unique, "short of duplicating its code under another name". ## Where the label is persisted - **Default table names** — `Patient` in label `patients` gets `patients_patient` unless `Meta.db_table` overrides it. - **Migrations** — each migration's `dependencies` names apps by label, for example `("patients", "0003_add_insurance")`, and the migration history table records the label. - **Content types** — `django.contrib.contenttypes` stores `app_label` and `model` for every model, and generic relations point at those rows. - **Permissions** — auth builds permission strings such as `patients.add_patient`, so `user.has_perm()` calls and group assignments embed the label. - **String references** — `ForeignKey("patients.Patient")`, `AUTH_USER_MODEL = "accounts.User"` and `apps.get_model("patients", "Patient")` all use it. ## Why changing it is risky The docs warn that changing `label` after migrations have been applied "will result in breaking changes to a project or, in the case of a reusable app, any existing installs of that app". Concretely: 1. Default table names no longer match the tables in the database. 2. Existing migration files and the recorded migration history refer to the old label, so Django sees a new app with unapplied migrations. 3. Content type rows and permission strings keep the old label, orphaning generic relations and breaking permission checks. ## How to decide safely - **Choose labels deliberately on day one**, especially for apps with generic names such as `auth`, `users`, `core` or `admin`. - **Relabel at install time, not later**: a label clash found before the first migration is a one-line fix. - **For reusable apps**, treat the label as public API and never change it in a release. ## Looking the label up at runtime Code should not hard-code assumptions about labels it does not own. The registry exposes them: - `apps.get_app_config("patients")` returns the config for a label and raises `LookupError` for an unknown one; - `Patient._meta.app_label` gives a model's label; - `apps.get_model("patients.Patient")` resolves a model from a label string. Using these instead of string-building table names or permission codes keeps code working if a project installs an app under a custom label, which is common for reusable apps that clash with a host project's own app names. ## What an interviewer is listening for A senior candidate distinguishes path from identifier, knows the uniqueness error and the relabel fix, and can list where the label is persisted — which is exactly why it is expensive to change.

  • Does changing verbose_name carry the same risk as changing label?
    No. `verbose_name` is display-only, defaulting to `label.title()`, and is used for things like the admin's app heading. It is not stored in table names, migrations, content types or permissions, so it can change freely between releases.
  • Can Meta.db_table reduce the blast radius of a label change?
    It pins table names, so tables no longer move with the label. It does not help with migration dependencies and history, content type rows or permission strings, which still embed the label, so a label change remains a migration project rather than a rename.

saying these in an interview costs you the question

  • Treats label as a cosmetic display name like verbose_name
  • Believes two apps may share a label if their names differ
  • Renames an app's label casually after it has shipped migrations
  • Thinks the label only affects the admin's app heading