Organizing Django Apps Inside an apps/ Directory

Instead of having all your apps clutter up your project's directory, organizing them in a dedicated 'apps/' directory makes the project more structured.

As soon as Django projects grow past just one or two apps, many teams move all first-party apps into a dedicated directory: apps/

Instead of:

project/
    blog/
    users/
    billing/
    orders/

they use:

project/
    apps/
        blog/
        users/
        billing/
        orders/

This is a common enterprise Django pattern because it creates a clear separation between:

  • project configuration
  • business applications
  • shared infrastructure
  • deployment/runtime files

This article explains:

  1. How to start a project with an apps/ directory
  2. How to refactor an existing Django project safely

Want to read more on Django apps in detail? Django Apps: Complete Guide

Why Use an apps/ Directory?

Default Django structure works well for small projects.

But as complexity grows, the root folder often becomes crowded:

project/
    blog/
    users/
    orders/
    billing/
    settings.py
    urls.py
    utils.py
    celery.py
    scripts/

This mixes business domains with framework configuration.

Using an apps/ package gives structure:

project/
    config/
    apps/
    common/

Way easier to navigate and maintain.

What Belongs in apps/

Only first-party Django apps that represent business capabilities.

Good examples:

  • apps/users
  • apps/blog
  • apps/orders
  • apps/billing
  • apps/projects
  • apps/support

Bad examples:

  • apps/utils
  • apps/helpers
  • apps/common_functions
  • apps/random_stuff

Those belong in shared/internal libraries, not Django apps.

project/
│
├── manage.py
│
├── config/
│   ├── settings/
│   ├── urls.py
│   ├── asgi.py
│   └── wsgi.py
│
├── apps/
│   ├── users/
│   ├── blog/
│   ├── billing/
│   ├── orders/
│   └── projects/
│
├── common/
├── requirements/
└── scripts/

We have a full article describing Django Enterprise Structure -> Django Enterprise-scale projects

Approach 1 — Using apps/ at Project Start

This is the easiest and cleanest moment to do it.

Step 1: Create Django Project

bash

1
django-admin startproject config .

Now you have:

config/
    settings.py
    urls.py

Step 2: Create apps/

apps/
    __init__.py

Important: apps/init.py makes it a Python package.

Step 3: Create Apps Inside It

bash

1
2
3
python manage.py startapp blog apps/blog
python manage.py startapp users apps/users
python manage.py startapp billing apps/billing

Step 4: Register Apps

In settings:

python

1
2
3
4
5
6
7
8
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",

    "apps.blog",
    "apps.users",
    "apps.billing",
]

Step 5: Configure Each apps.py

Example for apps/blog/apps.py:

python

1
2
3
4
5
6
from django.apps import AppConfig

class BlogConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "apps.blog"
    label = "blog"

Alter the name by prepending the apps parent directory name, 'apps' in this example. This gives django the relative path to your app.

That's why we define a label as well. This way we can still reference the app by just the name without having to carry the prefix inside every reference.

Example:

python

1
label = "blog"

This preserves clean references:

python

1
2
3
4
5
ForeignKey("blog.Post")

# instead of:

ForeignKey("apps.blog.Post")

Highly recommended.

We have a full guide on apps.py -> Django apps.py

Why Starting This Way Is Best

  • No broken imports.
  • No migration surprises.
  • No refactor cost later.

Approach 2 — Refactoring an Existing Project into apps/

This is very common.

You have:

project/
    blog/
    users/
    billing/

Now you want:

project/
    apps/
        blog/
        users/
        billing/

This can be done safely.

Important Warnings:

  • Do not casually rename apps without understanding migrations.
  • Django stores app labels in migrations and database metadata.
  • Move carefully.

Step 1: Create apps/

apps/
    __init__.py

Step 2: Move Folders

From:

blog/
users/
billing/

To:

apps/
    blog/
    users/
    billing/

Step 3: Update apps.py

Before:

python

1
2
class BlogConfig(AppConfig):
    name = "blog"

After:

python

1
2
3
class BlogConfig(AppConfig):
    name = "apps.blog"
    label = "blog"

When refactoring an existing project we have to define a label or if one existed keep it the same:

this helps preserve:

  • migration references
  • DB table names
  • ForeignKey strings
  • admin internals

Without it, Django may think it is a new app!

Step 4: Update INSTALLED_APPS inside settings

Rename the apps

From: "blog"

To: "apps.blog"

Step 5: Update Python Imports

Old:

python

1
2
from blog.models import Post
from users.forms import ProfileForm

New:

python

1
2
from apps.blog.models import Post
from apps.users.forms import ProfileForm

This can be the most tedious step since imports can be scattered. Search across the project and change the import paths.

Step 6: Test Startup

bash

1
2
python manage.py check
python manage.py runserver

Especially missed Python imports will show up here.

Step 7: Test Migrations

bash

1
2
python manage.py makemigrations
python manage.py migrate

If labels were preserved correctly, usually no issues.

Migration Safety Notes

If your original app was:

python

1
name = "blog"

and you change to:

python

1
2
name = "apps.blog"
label = "blog"

Django usually keeps migration identity stable.

So keeping label definition is the safest pattern.

Common Problems During Refactor

1. Forgot init.py

Rquired: apps/init.py

2. Wrong name

Wrong:

python

1
name = "blog"

after move.

Correct:

python

1
name = "apps.blog"

3. Missing label

Can break migration identity.

Use:

python

1
label = "blog"

4. Old Imports Still Exist

python

1
from blog.models import ...

5. Circular Imports Surface

Refactors often expose hidden bad imports.

Use:

python

1
ForeignKey("blog.Post")

or service-layer imports.

Should Third-Party Apps Go Inside apps/?

Short answer -> No.

Only your own apps go in /apps.

What About Shared Utilities?

Use another package:

  • common/
  • core/
  • shared/
  • lib/

Example:

  • common/email.py
  • common/storage.py
  • common/permissions.py

Not Django apps.

Complete Django /apps Refactor Checklist

Every Place Old App Paths Can Break After Moving Apps into an apps/ Directory

When you move apps from:

blog/
users/
billing/

to:

apps/
    blog/
    users/
    billing/

you must update every dotted Python path that references the old location.

Example:

python

1
2
3
4
blog.models
blog.views
blog.routing
blog.middleware

must become:

python

1
2
3
4
apps.blog.models
apps.blog.views
apps.blog.routing
apps.blog.middleware

Missing even one causes runtime errors.

Priority Order

Check your refactoring steps in this order:

  1. apps.py
  2. INSTALLED_APPS
  3. imports
  4. middleware
  5. urls
  6. Channels / ASGI
  7. signals
  8. Celery
  9. template tags
  10. management commands
  11. tests
  12. migrations
  13. admin
  14. external deploy config

Red Flags After Refactor

If you see:

No module named 'blog'

or:

Cannot import 'blog'

or:

LookupError: No installed app with label ...

You missed one of the locations above.

Validation Commands after Refactor or possible fixes:

Run:

bash

1
2
3
4
python manage.py check
python manage.py makemigrations --check
python manage.py migrate
python manage.py runserver

Then test:

  • admin
  • websocket pages
  • celery tasks
  • custom commands
  • templates
  • login/logout

If your Django project has 3+ internal apps or 2+ developers, use an apps/ directory early.

It becomes increasingly valuable over time.

Join the Newsletter

Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.

Get updates when new guides, learning paths, cheat sheets, and field notes are published.

No spam. Unsubscribe anytime.



There is no third-party involved so don't worry - we won't share your details with anyone.