django/django · error · ImproperlyConfigured

The app label '%s' is not a valid Python identifier.

Error message

The app label '%s' is not a valid Python identifier.

What it means

Raised in AppConfig.__init__ when the resolved app label fails Python's str.isidentifier() check. Django uses the label as a Python module-level identifier (e.g. for app_label.ModelName lookups, migrations, model _meta.app_label), so it must be a valid identifier. It is raised as django.core.exceptions.ImproperlyConfigured during app population.

Source

Thrown at django/apps/config.py:36

        self.name = app_name

        # Root module for the application e.g. <module 'django.contrib.admin'
        # from 'django/contrib/admin/__init__.py'>.
        self.module = app_module

        # Reference to the Apps registry that holds this AppConfig. Set by the
        # registry when it registers the AppConfig instance.
        self.apps = None

        # The following attributes could be defined at the class level in a
        # subclass, hence the test-and-set pattern.

        # Last component of the Python path to the application e.g. 'admin'.
        # This value must be unique across a Django project.
        if not hasattr(self, "label"):
            self.label = app_name.rpartition(".")[2]
        if not self.label.isidentifier():
            raise ImproperlyConfigured(
                "The app label '%s' is not a valid Python identifier." % self.label
            )

        # Human-readable name for the application e.g. "Admin".
        if not hasattr(self, "verbose_name"):
            self.verbose_name = self.label.title()

        # Filesystem path to the application directory e.g.
        # '/path/to/django/contrib/admin'.
        if not hasattr(self, "path"):
            self.path = self._path_from_module(app_module)

        # Module containing models e.g. <module 'django.contrib.admin.models'
        # from 'django/contrib/admin/models.py'>. Set by import_models().
        # None if the application doesn't have a models module.
        self.models_module = None

        # Mapping of lowercase model names to model classes. Initially set to

View on GitHub (pinned to ae25a40be0)

Solutions

  1. Rename the app's Python package directory to use only [A-Za-z_][A-Za-z0-9_]* (e.g. my_blog, not my-blog).
  2. If you cannot rename the package, create an apps.py with an AppConfig subclass declaring both `label = 'my_blog'` (a valid identifier) and `name = 'my-app'`, then reference that AppConfig in INSTALLED_APPS.
  3. Ensure custom AppConfig.label values are valid identifiers — letters, digits (not leading), and underscores only.

Example fix

# before
# INSTALLED_APPS = ['my-blog']  -> last component 'my-blog' fails isidentifier()

# after (option A): rename package
django_blog/   # package dir renamed

# after (option B): AppConfig override
# my_blog/apps.py
class MyAppConfig(AppConfig):
    name = 'my_blog'
    label = 'my_blog'
# settings.py: INSTALLED_APPS = ['my_blog.apps.MyAppConfig']
Defensive patterns

Strategy: validation

Validate before calling

import keyword
label = getattr(app_config_cls, 'label', app_name.rpartition('.')[2])
if not label.isidentifier() or keyword.iskeyword(label):
    raise ValueError(f'App label {label!r} is not a valid identifier; rename the package or set AppConfig.label.')

Type guard

import keyword

def is_valid_app_label(label: str) -> bool:
    return isinstance(label, str) and label.isidentifier() and not keyword.iskeyword(label)

Try / catch

try:
    app_config = MyAppConfig(name='myapp', module=app_module)
except ImproperlyConfigured as e:
    if 'not a valid Python identifier' in str(e):
        # rename package or override label, then retry population
        raise SystemExit(f'Fix app label: {e}')
    raise

Prevention

When it happens

Trigger: An INSTALLED_APPS entry resolves to a module whose last path component is not a valid identifier (e.g. 'my-app' or '2things'), or a custom AppConfig subclass sets label = 'some-thing' with a hyphen/digit-leading/space. The check at config.py:35 (`if not self.label.isidentifier()`) fires before any other init completes.

Common situations: Naming a Django app package with a hyphen (my-blog) instead of underscore (my_blog); copying a third-party package whose distribution name has a dash but importing the module by the wrong name; setting label on an AppConfig to a display string like 'My App'.

Related errors


AI-assisted analysis of django/django@ae25a40be0 (2026-08-06). Data as JSON: /api/errors/6d609d775204ff4a. Report an issue: GitHub.