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 toView on GitHub (pinned to ae25a40be0)
Solutions
- Rename the app's Python package directory to use only [A-Za-z_][A-Za-z0-9_]* (e.g. my_blog, not my-blog).
- 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.
- 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
- Use only snake_case identifiers for app package directories.
- Never set AppConfig.label to a display string.
- Add a CI lint check that asserts every INSTALLED_APPS entry's last component passes str.isidentifier().
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
- %r declares more than one default AppConfig: %s.
- Module '%s' does not contain a '%s' class.
- '%' isn't a subclass of AppConfig.
- '%' must supply a name attribute.
- Application labels aren't unique, duplicates: %s
AI-assisted analysis of django/django@ae25a40be0 (2026-08-06).
Data as JSON: /api/errors/6d609d775204ff4a.
Report an issue: GitHub.