Project Structure Best Practices
Recommended layout for apps, templates, static, media, settings packages, and shared utilities. Naming, import paths, and how to keep the project maintainable as it grows.
1. Introduction
A clean project structure makes your codebase easier to navigate, maintain, and scale. Django does not enforce a strict layout beyond the basics — which means developers often end up with messy structures as projects grow.
This guide shows you a practical, widely used layout for Django projects. It covers where to put apps, templates, static files, media, and settings so everything stays organized from day one.
- You should already have
myprojectset up with thepagesapp and split settings in place. - This page is more of a reference than a step-by-step guide — apply what makes sense for your project size.
2. The recommended layout
Here is a clean layout for a Django project with multiple apps:
myproject/
├── .env
├── .env.example
├── .gitignore
├── manage.py
├── requirements.txt
├── mysite/
│ ├── __init__.py
│ ├── urls.py
│ ├── asgi.py
│ ├── wsgi.py
│ └── settings/
│ ├── __init__.py
│ ├── base.py
│ ├── dev.py
│ └── prod.py
├── pages/
│ ├── migrations/
│ ├── templates/
│ │ └── pages/
│ │ └── index.html
│ ├── static/
│ │ └── pages/
│ │ └── pages.css
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── templates/
│ └── base.html
├── static/
│ ├── css/
│ ├── js/
│ └── images/
└── media/
This layout keeps project-wide files at the root level and app-specific files inside each app folder. Let us go through the important decisions.
3. App structure
Each app should handle one specific feature or domain. Keep apps focused — if an app is doing too many unrelated things, split it.
Inside each app, add a urls.py file to keep the app's URL patterns self-contained:
pages/
├── migrations/
├── templates/
│ └── pages/
│ └── index.html
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
├── urls.py ← create this yourself
└── views.py
Notice the template is placed inside templates/pages/ — not directly inside templates/. This namespacing prevents template name conflicts between apps. For example, if two apps both have an index.html, Django would load the wrong one without this namespacing.
pages, posts, accounts, orders. Avoid generic names like app, main, or core — they tell you nothing about what the app does.
4. Templates
There are two places templates can live and both are valid:
- Inside the app —
pages/templates/pages/index.html. Good for app-specific templates that are only used by that app. - Project-level —
templates/base.html. Good for shared layouts, base templates, and templates used across multiple apps.
Add the project-level templates folder to your settings so Django can find it:
# mysite/settings/base.py
TEMPLATES = [
{
...
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
...
}
]
With APP_DIRS: True and DIRS set, Django searches the project-level templates/ folder first, then each app's templates/ folder. Put your base.html in the project-level folder and extend it from app templates.
5. Static files
Like templates, static files can live in two places:
- Inside the app —
pages/static/pages/pages.css. Namespace it the same way as templates to avoid conflicts. - Project-level —
static/css/,static/js/,static/images/. For shared assets used across the whole project.
Add the project-level static folder to settings:
# mysite/settings/base.py
STATIC_URL = 'static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles' # used by collectstatic in production
6. Media files
Media files are files uploaded by users — profile photos, documents, attachments. They are kept completely separate from static files because they are dynamic and user-generated.
# mysite/settings/base.py
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'
The media/ folder is created at runtime when the first file is uploaded. Never commit the contents of media/ to Git — add it to .gitignore.
7. What goes in .gitignore
At minimum, your .gitignore should exclude:
# .gitignore
.env
.venv/
__pycache__/
*.pyc
*.pyo
db.sqlite3
media/
staticfiles/
8. Splitting large files
As apps grow, single files like models.py and views.py can become very long and hard to navigate. A common pattern is to convert them into packages:
pages/
├── models/
│ ├── __init__.py ← import all models here
│ ├── page.py
│ └── category.py
├── views/
│ ├── __init__.py
│ ├── page_views.py
│ └── category_views.py
The key is to import everything in __init__.py so the rest of your code still works with the same import paths:
# pages/models/__init__.py
from .page import Page
from .category import Category
9. Next steps
Your project is now structured cleanly for growth. The next step is to configure static and media settings properly so Django can find and serve your files correctly in both development and production.