FAQ & Troubleshooting
Answers to the most common questions and issues.
Installation & setup
My sysconfig.py is being ignored. Fields don't show up in the admin UI.
A few things to check:
1. Is the app in INSTALLED_APPS?
Autodiscovery only runs for apps listed in INSTALLED_APPS. If your app isn't there, its sysconfig.py is never imported.
2. Does the file use @register_config(...)?
Simply having a sysconfig.py file isn't enough — you must decorate your config class with @register_config("your_app_label"):
# ✅ Correct
@register_config("myapp")
class MyAppConfig:
...
# ❌ Wrong — not registered
class MyAppConfig:
...3. Did Django restart after you created the file?
Autodiscovery runs at startup. If you added sysconfig.py while the server was running, restart the dev server.
4. Is there a syntax error or import error in your sysconfig.py?
If the file raises an exception during import, autodiscovery silently skips it (or you'll see a traceback in the startup logs). Check your terminal output when starting the server.
The admin config UI at /admin/config/ returns a 404.
Make sure the config URL pattern is in your urls.py and comes before the default admin path:
urlpatterns = [
path("admin/config/", include("django_sysconfig.urls")), # ← first
path("admin/", admin.site.urls), # ← second
]If path("admin/", ...) comes first, Django matches it before ever reaching the config pattern.
I get a django.db.utils.OperationalError: no such table: django_sysconfig_configvalue.
You haven't run migrations yet:
python manage.py migrateReading and writing values
config.get(...) returns None but I set a default.
The most common cause is a typo in the path. Double-check:
- The app label matches the string in
@register_config("app_label") - The section name matches the class name (lowercased)
- The field name matches the attribute name (lowercased)
You can verify the path is registered:
from django_sysconfig.accessor import config
config.exists("myapp.general.site_name") # should be TrueIf it returns False, the path isn't registered — re-check your sysconfig.py.
AppNotFoundError raised even though my sysconfig.py exists.
The app label you're passing to config.get(...) must exactly match the string in @register_config("app_label"). They are case-sensitive.
@register_config("MyApp") # registered as "MyApp"
...
config.get("myapp.general.x") # ❌ "myapp" ≠ "MyApp" → AppNotFoundError
config.get("MyApp.general.x") # ✅ matchesConvention: use lowercase app labels to avoid this.
config.set(...) doesn't seem to take effect in other processes.
You're probably using the default LocMemCache (Django's in-memory per-process cache). When one process writes a value, it invalidates its own cache — but other processes have separate in-memory caches and won't see the change until their cache entry expires or is invalidated.
Fix: Switch to a shared cache backend like Redis in production:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
}
}See the Caching guide for details.
Encryption
After rotating SECRET_KEY, reading a secret field raises an error or returns garbage.
Encrypted values are keyed to your SECRET_KEY. Rotating the key makes all existing encrypted values unreadable.
Before rotating your key, read and store all secret field values:
secrets = {
"integrations.stripe.secret_key": config.get("integrations.stripe.secret_key"),
# ... all other SecretFrontendModel fields
}Then rotate SECRET_KEY, restart, and re-save the values:
config.set_many(secrets)See the Encryption guide for the full procedure.
A secret field I saved shows as blank/empty in the admin UI. Did the save fail?
No — this is expected behaviour. Secret fields are write-only in the admin UI. The input is always rendered empty, even when a value is stored. This is intentional to prevent credentials from being exposed in the browser.
To verify a secret value is saved, check config.is_set(...):
config.is_set("integrations.stripe.secret_key") # True if a value has been savedAdmin UI
Non-staff users see a login page when they visit /admin/config/. Is that correct?
Yes. Both config views require is_staff=True, identical to the Django admin. Non-staff users are redirected to the admin login page.
If you need to expose configuration to non-staff users, you'll need to build your own views using the config accessor.
Saving the config form in the admin doesn't seem to do anything / changes aren't reflected.
Check the following:
- Validation errors — if any field fails validation, the form is re-rendered with error messages. Look for red error text below any field.
- Cache backend — if you're running multiple processes without a shared cache, the process that received the save request has fresh values, but others may be stale. See the caching FAQ above.
on_savecallback exceptions — if a field has anon_savecallback that raises an exception, it will bubble up as a 500 error. Check your server logs.
Schema design
I renamed a field. Now config.get(...) raises FieldNotFoundError in production.
Renaming a field effectively creates a new field. The old database row isn't deleted — it becomes orphaned. Any code still referencing the old path will raise FieldNotFoundError.
Migration path:
- Add the new field name to your
sysconfig.pyalongside the old one (with the same default). - Deploy.
- Migrate any stored values from the old path to the new one:python
old_value = config.get("myapp.general.old_name") config.set("myapp.general.new_name", old_value) - Remove the old field from
sysconfig.pyand any code references. - Optionally, clean up the orphaned database row manually.
I removed a field from the schema. Is the old database row a problem?
No. Orphaned rows (rows in ConfigValue for paths that no longer exist in code) are ignored at runtime. They don't cause errors. You can clean them up manually with a management command or Django shell if you want to keep the table tidy:
from django_sysconfig.models import ConfigValue
ConfigValue.objects.filter(path="myapp.general.old_field").delete()Can I have the same field in two different apps?
Yes. Each field is scoped to its app_label.section.field path. appA.general.site_name and appB.general.site_name are entirely separate values.
Testing
How do I override config values in tests?
Use config.set(...) in your test setup, or patch the accessor directly:
from django_sysconfig.accessor import config
def test_maintenance_mode(client):
config.set("myapp.general.maintenance_mode", True)
response = client.get("/")
assert response.status_code == 503If you're using pytest, consider a fixture that resets values after each test:
import pytest
from django_sysconfig.accessor import config
@pytest.fixture(autouse=True)
def reset_maintenance_mode():
config.set("myapp.general.maintenance_mode", False)
yield
config.set("myapp.general.maintenance_mode", False)For unit tests that shouldn't touch the database at all, you can mock config.get:
from unittest.mock import patch
def test_uses_config_value():
with patch("myapp.views.config.get", return_value="Mocked Name"):
response = client.get("/")
assert b"Mocked Name" in response.contentSomething else isn't working
If your issue isn't covered here:
- Search the GitHub Issues — it may have been reported and answered already.
- Open a new issue with as much context as possible: Python version, Django version, your
sysconfig.py, and the full traceback if there is one.