Introduction

Flask gives developers a lot of freedom when organizing an application. That flexibility is useful for small projects, but as a Flask application grows, a poor folder structure can quickly lead to large route files, circular imports, duplicated logic, difficult testing, and configuration problems.

A scalable Flask project structure should make it clear where routes, business logic, database models, configuration, extensions, templates, and tests belong.

For most production applications, a structure like this is a strong starting point:

myapp/
├── app/
│   ├── __init__.py
│   ├── extensions.py
│   │
│   ├── blueprints/
│   │   ├── auth/
│   │   │   ├── __init__.py
│   │   │   └── routes.py
│   │   └── main/
│   │       ├── __init__.py
│   │       └── routes.py
│   │
│   ├── models/
│   │   ├── __init__.py
│   │   └── user.py
│   │
│   ├── services/
│   │   └── user_service.py
│   │
│   ├── templates/
│   │   ├── base.html
│   │   └── index.html
│   │
│   └── static/
│       ├── css/
│       ├── js/
│       └── images/
│
├── tests/
│   ├── conftest.py
│   ├── test_routes.py
│   ├── test_models.py
│   └── test_services.py
│
├── config.py
├── run.py
├── requirements.txt
├── .env
├── .env.example
└── .gitignore

This structure combines several important Flask practices:

  • Application factory pattern
  • Feature-based Blueprints
  • Centralized extension initialization
  • Separate business logic
  • Environment-specific configuration
  • Dedicated tests
  • Clear separation of concerns

If you’re completely new to the framework, read our guide on What is Flask? first.

Otherwise, let’s break down this structure and see how each part works.


Why Flask Project Structure Matters

A small Flask application can work perfectly well in a single file:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, World!"

if __name__ == "__main__":
    app.run(debug=True)

For a tutorial or quick prototype, there is nothing wrong with this.

Problems begin when the same file starts handling:

  • Authentication
  • Database models
  • API endpoints
  • Email
  • Payments
  • Admin functionality
  • Configuration
  • Background jobs
  • External integrations
  • Business rules

A single app.py can quickly become difficult to understand and maintain.

A proper Flask folder structure gives your application clear boundaries.

BenefitWhat It Helps With
ScalabilityAdd new modules without restructuring the entire application
MaintainabilityFind and modify code more easily
TestingTest routes, services, and models independently
CollaborationGive team members predictable places to add code
ConfigurationSeparate development, testing, and production settings
DeploymentPrepare the application for containers and production servers

The goal is not to create as many folders as possible.

The goal is to make the structure grow with the application.


Recommended Flask Project Structure

Let’s look at the main pieces of the structure.

1. app/ — Main Application Package

The app directory contains the application itself.

app/
├── __init__.py
├── extensions.py
├── blueprints/
├── models/
├── services/
├── templates/
└── static/

Its __init__.py file is normally where the Flask application is created.

Instead of creating the app globally, we can use the Flask application factory pattern.

from flask import Flask

from .extensions import db
from .blueprints.main import main_bp
from .blueprints.auth import auth_bp


def create_app(config_class="config.Config"):
    app = Flask(__name__)

    app.config.from_object(config_class)

    db.init_app(app)

    app.register_blueprint(main_bp)
    app.register_blueprint(auth_bp, url_prefix="/auth")

    return app

The function returns a fully configured Flask application.

We’ll examine this pattern in more detail shortly.


2. extensions.py — Initialize Flask Extensions

One common source of circular imports in Flask applications is initializing extensions directly against the application instance.

Instead, create the extension objects independently:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

Then initialize them inside create_app():

db.init_app(app)

You can keep multiple Flask extensions here:

from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager

db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()

Then connect them to the application:

db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)

This gives the application factory control over extension initialization and reduces tight coupling between modules.


Flask Application Factory Pattern

What Is the Flask Application Factory Pattern?

The Flask application factory pattern creates the Flask application inside a function instead of creating a single global instance.

A simple example looks like this:

from flask import Flask


def create_app():
    app = Flask(__name__)

    return app

You can then configure the application inside that function:

def create_app(config_class="config.Config"):
    app = Flask(__name__)

    app.config.from_object(config_class)

    return app

And add extensions and Blueprints:

from flask import Flask

from .extensions import db
from .blueprints.main import main_bp


def create_app(config_class="config.Config"):
    app = Flask(__name__)

    app.config.from_object(config_class)

    db.init_app(app)

    app.register_blueprint(main_bp)

    return app

Why Use an Application Factory?

The factory pattern becomes particularly useful as the application grows.

Multiple configurations

You can create different application instances for development, testing, and production.

development_app = create_app("config.DevelopmentConfig")
test_app = create_app("config.TestingConfig")

Easier testing

Tests can create isolated application instances without depending on a globally created app.

Cleaner extension initialization

Extensions such as SQLAlchemy can be created independently and initialized after the app is created.

Better modularity

Blueprints, middleware, error handlers, CLI commands, and extensions can all be registered during application creation.


Running an Application Factory

A simple run.py can create the app:

from app import create_app

app = create_app()

During development, Flask can also work directly with application factories when configured appropriately.

The important architectural idea is that application creation happens in one predictable place.


Organizing Flask with Blueprints

As the number of routes grows, placing all routes in one file becomes difficult to maintain.

Flask Blueprints allow related routes to be grouped into modules.

For example:

app/
└── blueprints/
    ├── auth/
    │   ├── __init__.py
    │   └── routes.py
    │
    ├── main/
    │   ├── __init__.py
    │   └── routes.py
    │
    └── admin/
        ├── __init__.py
        └── routes.py

This is often easier to scale than one large routes.py.


Flask Blueprint Example

Create the Blueprint:

app/blueprints/main/__init__.py

from flask import Blueprint

main_bp = Blueprint("main", __name__)

from . import routes

Now create its routes.

app/blueprints/main/routes.py

from flask import render_template

from . import main_bp


@main_bp.route("/")
def home():
    return render_template("index.html")


@main_bp.route("/about")
def about():
    return render_template("about.html")

Register it in the application factory:

from flask import Flask

from .blueprints.main import main_bp


def create_app():
    app = Flask(__name__)

    app.register_blueprint(main_bp)

    return app

Blueprints with URL Prefixes

You can also give a Blueprint a URL prefix.

For example:

app.register_blueprint(auth_bp, url_prefix="/auth")

Routes inside that Blueprint might then become:

/auth/login
/auth/register
/auth/logout

For an API:

app.register_blueprint(api_bp, url_prefix="/api/v1")

This makes URL organization much cleaner.


Organize Blueprints by Feature

A useful rule for larger Flask projects is:

Organize code around features or domains rather than creating one enormous folder for every technical type.

For example:

blueprints/
├── auth/
├── users/
├── products/
├── orders/
└── admin/

This makes it easier to understand which part of the application owns particular routes.

For larger modules, each Blueprint can contain more than routes:

products/
├── __init__.py
├── routes.py
├── forms.py
├── schemas.py
└── helpers.py

You do not need this complexity for every application.

Start simple and introduce additional separation when the feature actually requires it.


Organizing Database Models

Database models should normally live separately from route handlers.

Example:

app/
└── models/
    ├── __init__.py
    ├── user.py
    ├── product.py
    └── order.py

A simple SQLAlchemy model:

app/models/user.py

from app.extensions import db


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)

    username = db.Column(
        db.String(80),
        unique=True,
        nullable=False
    )

Keeping models separate makes your database layer easier to locate and maintain.


Add a Service Layer for Business Logic

One of the biggest mistakes in growing Flask projects is putting too much logic inside routes.

Consider this:

@users_bp.route("/users/<username>")
def get_user(username):
    user = User.query.filter_by(username=username).first()

    # More queries
    # Validation
    # Business rules
    # External API calls
    # Email logic

    return ...

Eventually, route handlers become difficult to test and reuse.

Move business logic into services.

app/
└── services/
    ├── user_service.py
    ├── payment_service.py
    └── email_service.py

Example:

app/services/user_service.py

from app.models.user import User


def get_user_by_username(username):
    return User.query.filter_by(
        username=username
    ).first()

The route becomes simpler:

from app.services.user_service import get_user_by_username


@users_bp.route("/users/<username>")
def get_user(username):
    user = get_user_by_username(username)

    if user is None:
        return {"error": "User not found"}, 404

    return {
        "id": user.id,
        "username": user.username
    }

This separation becomes increasingly valuable when business logic is reused by:

  • Web routes
  • REST APIs
  • Background jobs
  • CLI commands
  • Scheduled tasks

Templates and Static Files

Traditional server-rendered Flask applications normally keep HTML templates inside templates/:

templates/
├── base.html
├── index.html
├── auth/
│   ├── login.html
│   └── register.html
└── admin/
    └── dashboard.html

Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}My Flask App{% endblock %}</title>
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>

Static assets go inside static/:

static/
├── css/
├── js/
└── images/

For example:

<link
    rel="stylesheet"
    href="{{ url_for('static', filename='css/app.css') }}"
>

For API-only Flask applications, you may not need either directory.


Flask Configuration Best Practices

Application configuration should not be scattered throughout route files.

A basic config.py can define shared and environment-specific settings.

import os


class Config:
    SECRET_KEY = os.getenv("SECRET_KEY")
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.getenv(
        "DATABASE_URL",
        "sqlite:///development.db"
    )


class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"


class ProductionConfig(Config):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.getenv(
        "DATABASE_URL"
    )

Your factory can accept a configuration class:

def create_app(config_class="config.DevelopmentConfig"):
    app = Flask(__name__)

    app.config.from_object(config_class)

    return app

For testing:

app = create_app("config.TestingConfig")

And for production:

app = create_app("config.ProductionConfig")

Keep Secrets Outside Source Code

Secrets should not be hard-coded into config.py.

Avoid this:

SECRET_KEY = "my-production-secret"

Instead:

import os

SECRET_KEY = os.getenv("SECRET_KEY")

A local .env file might contain:

SECRET_KEY=your-local-secret
DATABASE_URL=postgresql://user:password@localhost/myapp

You can load environment variables using python-dotenv where appropriate.

pip install python-dotenv

Never commit the real .env file containing credentials.

Instead, commit an example file:

.env.example

Example:

SECRET_KEY=
DATABASE_URL=

This documents the required variables without exposing real secrets.


Testing Structure for Flask

A scalable Flask project should treat tests as part of the architecture rather than adding them later.

A straightforward structure is:

tests/
├── conftest.py
├── test_routes.py
├── test_models.py
└── test_services.py

As the test suite grows, you can mirror your application structure:

tests/
├── blueprints/
│   ├── test_auth.py
│   └── test_products.py
│
├── services/
│   ├── test_user_service.py
│   └── test_payment_service.py
│
└── models/
    └── test_user.py

Flask Testing with Pytest

Install pytest:

pip install pytest

Create an application fixture.

tests/conftest.py

import pytest

from app import create_app
from app.extensions import db


@pytest.fixture
def app():
    app = create_app("config.TestingConfig")

    with app.app_context():
        db.create_all()

        yield app

        db.session.remove()
        db.drop_all()


@pytest.fixture
def client(app):
    return app.test_client()

Now test a route:

def test_home_route(client):
    response = client.get("/")

    assert response.status_code == 200

The application factory makes this pattern much easier because each test session can create an application using a dedicated testing configuration.


Small vs Medium vs Large Flask Project Structure

There is no single folder structure that every Flask application should use.

Your structure should reflect the application’s complexity.

Small Flask Project

For a very small application:

myapp/
├── app.py
├── templates/
├── static/
└── requirements.txt

Use this when:

  • Building a prototype
  • Learning Flask
  • Creating a tiny internal utility
  • Running only a handful of routes

Do not introduce unnecessary architecture before you need it.


Medium Flask Project

Once the app has multiple features, a database, authentication, or tests, move toward:

myapp/
├── app/
│   ├── __init__.py
│   ├── extensions.py
│   ├── blueprints/
│   ├── models/
│   ├── services/
│   ├── templates/
│   └── static/
│
├── tests/
├── config.py
├── run.py
└── requirements.txt

This structure is suitable for many real-world Flask applications.


Large Flask Project

A larger application might require deeper feature boundaries:

myapp/
├── app/
│   ├── __init__.py
│   ├── extensions.py
│   │
│   ├── auth/
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── forms.py
│   │   └── schemas.py
│   │
│   ├── products/
│   │   ├── routes.py
│   │   ├── services.py
│   │   ├── repositories.py
│   │   └── schemas.py
│   │
│   ├── orders/
│   │   ├── routes.py
│   │   ├── services.py
│   │   └── schemas.py
│   │
│   └── common/
│       ├── exceptions.py
│       └── utilities.py
│
├── tests/
├── migrations/
├── config.py
└── requirements.txt

You might introduce:

  • Repository layers
  • Schema/serialization layers
  • Dedicated API modules
  • Domain services
  • Background jobs
  • Caching
  • Message queues
  • Logging infrastructure

But these should solve real architectural problems.

Adding complexity purely because an application might become large someday usually makes development harder rather than easier.


REST API Project Structure

For an API-focused Flask application, the structure can be adjusted:

app/
├── __init__.py
├── extensions.py
│
├── api/
│   ├── __init__.py
│   ├── users.py
│   ├── products.py
│   └── orders.py
│
├── models/
├── services/
└── schemas/

Example Blueprint:

from flask import Blueprint, jsonify

api_bp = Blueprint("api", __name__)


@api_bp.route("/status")
def status():
    return jsonify({
        "status": "ok"
    })

Register it:

app.register_blueprint(
    api_bp,
    url_prefix="/api/v1"
)

This provides routes such as:

/api/v1/status

Background Jobs

Tasks such as:

  • Sending emails
  • Processing files
  • Generating reports
  • Synchronizing external APIs
  • Processing images
  • Running long calculations

should generally not block normal HTTP requests.

A growing Flask application may therefore add a background job system such as Celery.

Your structure might become:

app/
├── tasks/
│   ├── email_tasks.py
│   └── report_tasks.py

Keep background-task orchestration separate from route handlers and reuse your service layer where possible.


Flask Project Structure with Docker

Containerization does not replace good application structure, but a clean structure makes container deployment easier.

A basic project might contain:

myapp/
├── app/
├── tests/
├── config.py
├── run.py
├── requirements.txt
├── Dockerfile
├── .dockerignore
└── .env

A basic Dockerfile:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["gunicorn", "run:app", "--bind", "0.0.0.0:8000"]

For production, configuration such as database credentials and secret keys should be injected through the deployment environment rather than baked into the image.


Common Flask Project Structure Mistakes

A good folder structure is not just about what you include. It is also about avoiding patterns that make the application harder to maintain.

1. Keeping Everything in app.py

This usually works initially.

Later, the same file ends up containing:

  • Routes
  • Models
  • Validation
  • Business logic
  • Database queries
  • Authentication
  • Configuration
  • API calls

Move to modules before one file becomes the entire application.


2. Putting Business Logic Inside Routes

Routes should primarily handle:

  1. Request input
  2. Validation
  3. Calling application logic
  4. Returning a response

Complex business rules belong in services or other appropriate domain modules.


3. Creating One Giant routes.py

Splitting app.py into one 3,000-line routes.py does not solve the architecture problem.

Group routes by feature using Blueprints.


4. Initializing Extensions Globally Against the App

This can create dependencies that make factories and tests difficult.

Prefer:

db = SQLAlchemy()

followed by:

db.init_app(app)

inside the application factory.


5. Hard-Coding Secrets

Never place production secrets directly inside committed source files.

Use environment variables or a suitable secrets-management system.


6. Mixing Database Queries Everywhere

If complicated queries are repeated across routes, services, background jobs, and commands, maintenance becomes difficult.

Centralize reusable data-access logic when the complexity justifies it.


7. Creating Too Many Layers Too Early

The opposite problem also exists.

A five-route Flask application probably does not need:

controllers/
repositories/
interfaces/
use_cases/
adapters/
domain/
infrastructure/

Use architecture to reduce complexity—not to manufacture it.


8. No Dedicated Test Structure

If tests are scattered or depend heavily on a globally configured application, they become harder to maintain.

The application factory plus a dedicated tests/ directory gives you a much cleaner testing foundation.


Flask Folder Structure Best Practices

For most growing Flask applications, follow these principles.

Use the application factory

Create the application through create_app() when you need configurable instances, modular initialization, or cleaner testing.

Use Blueprints for feature separation

Group related routes instead of putting every endpoint into one file.

Keep routes thin

Move reusable business logic into services or other appropriate modules.

Centralize extensions

Keep SQLAlchemy, migration tools, authentication extensions, and similar integrations in a predictable location.

Separate configuration

Keep development, testing, and production configuration distinct.

Keep secrets outside the repository

Use environment variables rather than committed credentials.

Add tests early

A clear testing structure becomes increasingly valuable as the application grows.

Scale the architecture gradually

Do not copy a huge enterprise folder structure into a tiny project.


Production-Ready Flask Project Checklist

Before considering your Flask structure ready for a serious application, check the following:

  • Application creation is centralized
  • Routes are grouped logically
  • Large applications use Blueprints
  • Database extensions are initialized cleanly
  • Business logic is separated from complex route handlers
  • Development and production configurations are separate
  • Secrets are stored outside source control
  • .env is excluded from Git
  • .env.example documents required variables
  • Database models have a predictable location
  • Tests live in a dedicated test directory
  • Application factory supports test configuration
  • Static assets and templates are organized logically
  • Production deployments do not rely on Flask’s development server
  • Logging and error handling are configured appropriately
  • Dependencies are tracked
  • The project README explains setup and architecture

Frequently Asked Questions

What is the best Flask project structure?

There is no universal structure for every Flask application. For a growing production project, a strong starting point is an app/ package containing Blueprints, models, services, extensions, templates, and static files, combined with an application factory and a separate tests/ directory.


When should I use Flask Blueprints?

Use Blueprints when your application has multiple groups of related routes or features such as authentication, administration, products, orders, or APIs.

They help keep routes modular without requiring separate Flask applications.


What is the Flask application factory pattern?

The Flask application factory pattern creates the application inside a function such as:

def create_app():
    app = Flask(__name__)

    return app

It makes application initialization easier to configure, test, and extend.


Should Flask business logic be inside routes?

Simple route-specific logic may be fine inside a route handler.

For larger applications, reusable or complex business logic is usually better placed in services or domain-specific modules so routes remain focused on HTTP request and response handling.


Should every Flask project use this full structure?

No.

A tiny Flask application may only need:

app.py
templates/
static/

Introduce Blueprints, services, factories, and deeper folder structures as application complexity increases.


Where should Flask database models go?

A common approach is:

app/
└── models/

Larger projects can split models by feature or domain.

The exact folder name matters less than keeping the structure predictable and consistent.


Conclusion

A good Flask project structure should make the application easier to understand today and easier to extend tomorrow.

For most growing applications, the strongest foundation is:

Application Factory
        ↓
Blueprints
        ↓
Routes
        ↓
Services
        ↓
Models / External Systems

Combined with:

extensions.py
config.py
tests/
templates/
static/

The most important principle is not to follow one folder layout blindly.

Start with the simplest architecture your application needs, then introduce structure as complexity grows.

For a production-oriented Flask application, using Blueprints, an application factory, separated configuration, centralized extensions, a dedicated testing setup, and clear business-logic boundaries gives you a codebase that is considerably easier to scale and maintain.

Categorized in: