In Flask, blueprints are a way of organizing your app into functional units. Rather than having one giant templates folder with hundreds of html files, and a single app.py file with dozens or hundreds of routes, you can split them up based on what they do. It took me a minute to really understand the concept, but that’s really how you have to think about blueprints, as conceptual units.

black and white sketch of a woman with hair in a bun, wearing a safety vest and hard hat, drafting a blueprint of a house

I’ve seen blueprints explained as organizing your Flask app based on what the app does. But that’s not quite right. Not really. A better way to think of it is as organizing the app based on what the user does with the app.

For my ProsePal app, I have five blueprints, core that handles static pages (static is a reserved name in Flask), accounts that handles things like signups, login and logout, and letting user’s manage their account information, free where my free apps go, paid where the paid products go (see what I mean by conceptual units?), and then a final one to handle payments with Stripe.

You may be asking why we don’t have a blueprint for Supabase then. The answer is because your users (frontend) will be interacting with Stripe, but only Flask (backend) will be interacting with Supabase. While blueprints are a part of Flask, and, therefore, the backend logic, they are a method of organizing how the frontend is set up.

Now, if a library is just a folder in Python with a specific file, then a B\blueprint is a folder in Flask with specific line of code. Same general principle.

But to start off we are going to create a couple folders.

Actually. Back up. To start off, we are going to talk about structures.

Flask blueprint structures

There are two main structures for Flask Blueprints, functional and divisional.

Functional Blueprints

Now, everything is all just folders, remember. And blueprint structures just are a way of sorting what files go in what folders. A functional blueprint makes outer folders based on function (note: not function not functions). So there is one folder for views, one for forms, another for templates, and then each folder then gets a file named after the blueprint.

src/
├── __init__.py
├── forms/
│   ├── account_forms.py (account specific forms)
│   ├── core_forms.py (core specific forms)
│   └── paid_forms.py (paid specific forms)
├── views/
│   ├── accounts.py (account related routes and logic)
│   ├── core.py (core related routes and logic)
│   └── paid.py (paid related routes and logic)
├── static/      (global static content)
└── templates/
    ├── accounts/  (account templates)
    │   └── ...
    ├── core/      (core templates)
    │   └── ...
    └── paid/       (paid templates)
        └── ...

Divisional Blueprints

Divisional blueprints go the other way. They say, what we really want to organize by is the blueprint. The folders are named for the blueprint, and then the files inside are named for the function.

src/
├── __init__.py
├── accounts/
│   ├── __init__.py
│   ├── forms.py
│   ├── views.py
│   └── templates/
│       └── ...
├── core/
│   ├── __init__.py
│   ├── forms.py
│   ├── views.py
│   └── templates/
│       └── ...
├── paid/
│   ├── __init__.py
│   ├── forms.py
│   ├── views.py
│   └── templates/
│       └── ...
├── static/
└── templates/
    └── base.html

It’s up to you which you prefer, but, personally, divisional structure makes more sense to me and create more flexibility, so that’s what will be used in this tutorial.

To start off, we will keep things simple. We will have three blueprints. core to keep our static pages/routes, accounts to handle account related stuff, and paid for our actual SaaS app. We will need to create another one for Stripe, but we can worry about that when it is time to integrate the payment processor.

So now we are going to create some folders. As you can probably guess from the examples, we will have three blueprints to start, accounts, core, and paid. So, let’s create three folders inside our src directory and put an empty __init__.py file in each one.

screenshot of project explorer pane in VSCode, showing accounts, core, and paid folders, each with an __init__.py file, nested in the src folder, with its own __init__.py file. In the project root, there is a .venv directory, and a .env and config.py files
View of project explorer pane in VSCode after creating the three folders and their init dunders

We’ve created a library for each blueprint in our Flask app. Now it’s time to turn them into actual blueprints.

Creating a blueprint

In each of these three libraries, create a views.py file and add the following code, changing blueprint_name to the name of your actual blueprint, in this case either accounts, core, or paid.

from flask import Blueprint

blueprint_name_bp = Blueprint("blueprint_name", __name__, template_folder="templates")

We are importing the Blueprint class from Flask. Then we are initializing an instance of the class under a variable name. By convention, we use the blueprint’s name followed by the letter’s bp, in snake case, but you can call it whatever you want. While initializing the instance, you pass the name of the blueprint, the import name, and the template folder path as arguments.

__name__ tells Python to refer to the name argument when it needs the import name. The template folder path will be relative to the blueprint’s root path. If you were using a functional structure for your blueprints, you wouldn’t use the template_folder argument, because your templates wouldn’t be in a folder that was relative to a blueprints folder.

There are other arguments you can pass as well. I’m not in the habit of splitting up my static files by blueprint. Almost all of them are used by multiple blueprints, so it doesn’t make sense to me. But you could set static_folder and have your static files for your blueprint’s pages served from within the blueprint. There are other options as well, such as setting a nested directory structure, like putting the accounts pages under https://yourapp.com/accounts/ but you can also do this within the route definition. Another option is if you wanted to set the pages for a route within a subdomain.

You can see all of the parameters available in the Flask documentation. While I would love if all of the parameters had practical examples, as far as docs for a library go, Flask’s are really pretty extensive.

Registering the blueprint

Now that we have our three views.py files, one in each blueprint folder, it’s time to register the blueprints with the app itself. To do that, we need to edit the __init.py file in the src directory. Registering a blueprint involves 2 steps:

  1. import the blueprint variable you assigned the class instance to
  2. call the register_blueprint method of your Flask instance with the variable as an argument

So now our src/__init__.py file looks like this:

from decouple import config
from flask import Flask

# Config classes for environment variables
from config import DevelopmentConfig, TestingConfig, StagingConfig, ProductionConfig

# Import blueprints
from accounts.views import accounts_bp
from core.views import core_bp
from paid.views import paid_bp

app = Flask(__name__)

# Register the blueprints
app.register_blueprint(accounts_bp)
app.register_blueprint(core_bp)
app.register_blueprint(paid_bp)

env_config = {
    "development": DevelopmentConfig,
    "testing": TestingConfig,
    "staging": StagingConfig,
    "production": ProductionConfig
}

# Load environment variable for flask environment configuration class
config_name = config("FLASK_ENV", default="development")
app.config.from_object(env_config[config_name])

Avoiding circular imports

What circular imports?

Good question, I’m glad you asked. 😉

Right now we don’t have any. But, all of our configuration settings are stored in app.config right now. Eventually, we will have routes in our views.py files that depend on those config settings. Access to our enviornment variables may eventually be routed through a Config class.

One example of routing environment variables through a Config class is your Stripe key. Stripe offers a test key and a live key. You can use the test key in development and when used in conjunction with some special false credit card numbers, you can test your payment systems without getting charged. So you can set STRIPE_TEST_KEY and STRIPE_LIVE_KEY in you .env file and then assign STRIPE_SECRET_KEY to STRIPE_TEST_KEY in DevelopmentConfig and STRIPE_LIVE_KEY in ProductionConfig.

If we hadn’t just imported something from these views.py files, we would just import app into views. But we can’t do that and import views into app. That’s a circular import, and creates an infinite loop of Python importing one module into the other forever until it crashes.

Instead, when you need to access app.config or some other method of your Flask instance, you instead will use a proxy called current_app. We will get into this more later, when we actually need it, but for now, just be aware, that when we import current_app from the flask library in a views module, we can use it in place of importing app and then use it exactly as you would if you add imported the app instance.

So instead of

STRIPE_KEY=app.config("STRIPE_SECRET_KEY")

it would be

STRIPE_KEY = current_app.config("STRIPE_SECRET_KEY")

And that’s it for today. Now that we have our configuration classes and blueprints set up, next, it’s time to set our sites on Supabase.