# django-split-settings turns one settings file into an ordered set of them

> A small Django library with no dependencies that includes settings files in order, shares one context between them, treats a file as optional, and can pull in a directory with a wildcard while leaving the ordering to glob.

**wemake-services/django-split-settings** — Organize Django settings into multiple files and directories. Easily override and modify settings. Use wildcards and optional settings files.

- Repository: https://github.com/wemake-services/django-split-settings
- Website: http://django-split-settings.rtfd.io
- Stars: 1,200 · Forks: 72
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/wemake-services-django-split-settings

## What the library actually changes in a Django project

Django ships with a single settings module, and that file grows. django-split-settings does not replace Django's settings mechanism, it changes the shape of the file: instead of one settings.py you have a package of settings files that are pulled into one shared context in a defined order. The README states the three features it adds, and they are all about control of composition. You can split settings across multiple files and directories, override and modify what earlier files declared, and mark a file as optional so a machine without it still starts.

The package itself has no dependencies, which is the design decision that makes it easy to adopt. It does not need a configuration library, a dotenv reader or a settings schema validator, so adding it does not add a supply chain to your project. What you get is composition with a small API surface: two functions, include and optional, imported from split_settings.tools.

The README also points at the reasoning behind the approach, a 2017 blog post about managing Django settings, and at an example project, wemake-django-template, that shows the pattern in a full application. That is worth doing before you restructure your own project, because the library gives you a mechanism and the blog post explains the conventions that make it readable.

## include and optional: the whole API in one snippet

The minimal example replaces the existing settings.py with a list of components, and the library prefers a settings package holding all of them:

```python
from split_settings.tools import optional, include

include(
    'components/base.py',
    'components/database.py',
    optional('local_settings.py')
)
```

The order of the arguments is the order of application. The README says that base.py and database.py are included in that order from the components/ subdirectory, and that local_settings.py in the same directory is included if it exists. The optional wrapper is the whole difference between a file that must be there and one that may be missing, and it exists for the classic case of a developer-local file that is deliberately not committed.

The design is worth comparing to the alternative it replaces. Without this library, the usual way to layer settings is to import modules in a single file and let Python's import machinery do the work, which works but obscures the order of operations. Here the order is written out as data, in one list, and a reader can see the whole configuration sequence in one screen.

One point the README does not spell out is what include returns or what the settings module should look like afterwards. The examples show calls with no assignment, so the practical assumption is that the included files execute in the module namespace of the settings module, and that this is why the local context matters.

## One shared context is what makes later files able to override earlier ones

The README highlights this in a note, and it is the mechanism that distinguishes this library from a directory of files that merely happen to be imported. The local context is passed on to each file, so each following file can access and modify the settings declared in the previous files. In other words, the files are not isolated modules with their own namespaces, they are executed in sequence against one running context.

That is what makes the override pattern work, and the README shows it in the environment specific file. A local settings file imports a name from a component and extends it:

```python
# local_settings.py
from components.base import INSTALLED_APPS

INSTALLED_APPS += (
  'raven.contrib.django.raven_compat',
)
```

The import is the slightly unusual part. The file is already executing inside a context that contains INSTALLED_APPS, but the example imports it explicitly from components.base, which makes the dependency visible in the file and keeps the original tuple intact, so the component that declared it can still be read and reasoned about. The result is additive override, the pattern most settings layers need for local tweaks.

The trade-off of the shared context is that the order of the include list becomes semantically significant, and a file that reads a name it expects from an earlier file will break if the list is reordered. Nothing in the README enforces that order, so it is a convention the team has to keep.

## Wildcards work, but glob order is not alphabetical

The include call accepts wildcards in file paths, which is convenient for a directory of related files:

```python
include('components/my_app/*.py')
```

The README is careful about what that means, and this is the sharpest edge in the library. Files are included in the order that glob returns them, probably in the same order as what ls -U would list them, and the files are NOT in alphabetical order.

That is a real trap rather than a technicality. If a directory holds a base file and an override file, an author who expects the override to be applied last will get a different result depending on the order the filesystem happens to return, and the same code can behave differently on two machines or after a checkout. The fix is to not rely on the wildcard for anything order sensitive, to keep the files it matches independent of each other, and to move anything that must run after a specific file into an explicit include entry.

The lazy evaluation angle is worth noting too. Because the list of matched files is only known at include time, a mistake in one of the matched files surfaces as a settings error at start up rather than as an import error at the point of use, which is a slightly worse error message but an earlier one.

## The BASE_DIR trap when settings stop being a single file

The README devotes a section to a consequence of the split that will bite anyone migrating an existing project. The django create-project command creates a BASE_DIR variable in settings.py, and that variable is usually used to locate static files, media files and templates. The generated code walks up the directory tree from the settings file:

```python
# Created by django create-project
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles/")
MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles/")
```

The expression means: take the path of the current file, take the parent folder, whatever you named the project, then the parent of that, the project root. So STATIC_ROOT evaluates to a staticfiles directory at the root of the project or repository.

With django-split-settings, settings is a module instead of a file, and the same expression now evaluates to the project directory rather than its parent, because the module file sits one level deeper. The README states the fix, which is to add one more dirname call:

```python
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
```

The reason this deserves its own section is that it fails quietly. Nothing raises an error, static files simply end up in the wrong place, and the symptom appears later as missing media in a deployment. The same arithmetic applies to any other path built from BASE_DIR, so the migration checklist is to look for every use of it rather than only the two lines in the generated file.

## Installing it and knowing which versions it claims

Installation is a single pip command, and the package ships without dependencies of its own:

```bash
pip install django-split-settings
```

The requirements section is more interesting than the install line. The README says the package will most likely work with most versions of Django, but officially supports 4.2, 5.0, 5.1, 5.2 and 6.0, and points to the test workflow as the definition of that list. If you need older Python or Django versions, the README advises using an older version of django-split-settings instead, which is the sensible instruction for a library that a project is likely to pin for years.

The packaging metadata says Python 3.10 or newer and below 4.0, and the development dependency on Django allows 4.2 up to 6.1, a range one version wider than the officially supported list in the README. The classifier set also lists Django 3.2, 4.0 and 4.1, which are older than the officially supported set, so the metadata carries the project's history as well as its current position. The project is on Read the Docs, and the development tooling is a strict stack: mypy, pytest with coverage and random ordering, the wemake-python-styleguide, doc8 for the documentation, and Sphinx for the docs.

The practical read for an adopter is that this is a maintained, tested library with a pinned support matrix, and that the matrix is the thing to check against your own Django version before you restructure anything.

## Maintenance, licence and the honest comparison

The last push to the repository was on 2026-10-12, so the project is being worked on, but the newest tagged release is 1.3.2 from 2024-07-05, and the version in the packaging metadata is still 1.3.2. The changelog lives in CHANGELOG.md and the README points there for version history. Practically, the installed package and the repository head share a version number, so anything committed since July 2024 is available from the source tree rather than from PyPI.

The licence needs a small correction before you rely on it. The repository metadata reports BSD-3-Clause, while the pyproject file records the licence as BSD-2-Clause, and the LICENSE file at the root is the document that settles it. That kind of mismatch is ordinary in older projects and harmless in practice, but if your organisation tracks licences by identifier, read the file rather than the badge.

The alternative to this library is not another package so much as a different arrangement of your own code. A single settings.py that reads environment variables, or a settings package you assemble yourself with plain imports, gives you the same result with no new dependency and no include order to remember. The difference is that django-split-settings makes the composition order explicit and gives you optional files and wildcards in one small API. If your problem is one enormous file, the library buys you clarity. If your problem is that settings differ per environment, a settings module driven by environment variables is a different and arguably better answer, and the README does not address that case at all.

## Conclusion

Adopt django-split-settings when a Django project has outgrown one settings.py, when local overrides must stay out of version control, and when your team is comfortable with import order deciding the final configuration. Do not adopt it for a small project, for settings that must be resolved purely from environment variables, or where the alphabetical ordering of globbed files would be convenient, since the library explicitly does not sort them. Verify first by checking which Django version your project runs against, because the supported list ends at 6.0 while the development dependency allows 6.1, and by reading the LICENSE file, since the repository metadata and the pyproject file name different BSD variants.

## FAQ

### How do I install django-split-settings?

Run pip install django-split-settings. The package has no dependencies of its own and needs Python 3.10 or newer, and the README states official support for Django 4.2, 5.0, 5.1, 5.2 and 6.0. For older Python or Django versions it advises pinning an older release of the library.

### How do I make a Django settings file optional with django-split-settings?

Wrap the path in the optional function imported from split_settings.tools, for example optional('local_settings.py'). The file is then included only if it exists, which is how a developer-local settings file stays out of version control without breaking start up elsewhere.

### In what order does django-split-settings include files matched by a wildcard?

In the order that glob returns them, which the README says is probably the same order as ls -U would list them, and explicitly not alphabetical order. Files that depend on each other should therefore be included explicitly rather than matched by a wildcard.

### Why did BASE_DIR break after I split my Django settings?

Because settings is now a module rather than a single file, so the generated expression os.path.dirname(os.path.dirname(os.path.abspath(__file__))) resolves to the project folder instead of its parent. The README fixes it by adding one more dirname call, and the same change is needed for any other path built from BASE_DIR.

### Can a later settings file change a value set in an earlier one?

Yes. The local context is passed on to each included file, so each following file can access and modify the settings declared in the previous ones. The README shows the pattern of importing a name such as INSTALLED_APPS from a component and extending it with a tuple.

## Sources

- [License: BSD-3-Clause](https://github.com/wemake-services/django-split-settings/blob/master/LICENSE)
- [Project website](http://django-split-settings.rtfd.io)
- [README](https://github.com/wemake-services/django-split-settings/blob/master/README.md)
- [Releases](https://github.com/wemake-services/django-split-settings/releases)
- [wemake-services/django-split-settings on GitHub](https://github.com/wemake-services/django-split-settings)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wemake-services-django-split-settings
