Skip to main content

Blueprint Resources and Static Files

When you organize a large Flask application into modules, you often need to keep assets like CSS, JavaScript, and HTML templates alongside the code that uses them. If you place these files in a central directory, the project structure becomes difficult to navigate as it grows. Flask uses the Blueprint class to allow each module to manage its own independent set of resources.

Defining Resource Folders

To associate a directory of static files or templates with a module, pass the static_folder and template_folder arguments when initializing a Blueprint. These paths are relative to the blueprint's root_path, which Flask automatically determines based on the import_name (usually __name__).

In the Flask tutorial's blog module (examples/tutorial/flaskr/blog.py), the blueprint is defined simply:

bp = Blueprint("blog", __name__)

If you wanted this blueprint to serve its own static files and templates from subdirectories, you would configure it like this:

admin_bp = Blueprint(
"admin",
__name__,
template_folder="templates",
static_folder="static"
)

Internally, Blueprint inherits from Scaffold, which handles the resolution of these paths. When you provide a static_folder, Flask automatically registers a route to serve those files.

Serving Static Files

When a blueprint has a static_folder configured, Flask adds a special route named static to that blueprint. You can generate URLs for these assets using url_for by prefixing the endpoint with the blueprint's name.

If you have a file at my_project/admin/static/style.css, and your blueprint is named admin, you generate the URL in a template or view like this:

flask.url_for("admin.static", filename="style.css")
# Returns "/admin/static/style.css" (assuming default url_prefix)

The actual serving is handled by Blueprint.send_static_file(filename), which uses flask.helpers.send_from_directory to safely deliver the file from the filesystem.

Cache Control for Blueprint Assets

By default, blueprint static files follow the application's global cache settings. You can control how long browsers should cache these files by setting SEND_FILE_MAX_AGE_DEFAULT in the app configuration.

If you need specific caching logic for a particular blueprint, you can override get_send_file_max_age in a subclass, as seen in tests/test_blueprints.py:

class MyBlueprint(flask.Blueprint):
def get_send_file_max_age(self, filename):
return 100 # Cache for 100 seconds

blueprint = MyBlueprint(
"blueprint", __name__, static_folder="static"
)

Locating Templates

When you call render_template, Flask searches through all registered template folders. The application's main templates folder has the highest priority, followed by the template folders of registered blueprints in the order they were registered.

Because multiple blueprints might have a file named index.html, you should namespace your templates by placing them in a subdirectory named after the blueprint. For example, the tutorial in examples/tutorial/flaskr/blog.py uses this pattern:

@bp.route("/")
def index():
# The template is located at flaskr/templates/blog/index.html
return render_template("blog/index.html", posts=posts)

If you define template_folder="templates" in a blueprint named auth, Flask will look for templates in auth/templates/. By using render_template("auth/login.html"), you ensure that Flask finds the correct file even if another blueprint also has a login.html.

Accessing Internal Resources

Sometimes you need to read a file bundled with your blueprint that isn't meant to be served directly to users, such as a configuration schema or a data file. The Blueprint.open_resource() method allows you to open these files relative to the blueprint's root directory.

# Inside a blueprint view
with bp.open_resource("schema.sql", mode="r") as f:
schema = f.read()

The open_resource method in src/flask/blueprints.py ensures the path is correctly joined with the blueprint's root_path:

def open_resource(
self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"
) -> t.IO[t.AnyStr]:
if mode not in {"r", "rt", "rb"}:
raise ValueError("Resources can only be opened for reading.")

path = os.path.join(self.root_path, resource)

if mode == "rb":
return open(path, mode)

return open(path, mode, encoding=encoding)

This method only supports reading ("r", "rt", or "rb") to prevent accidental modification of application code or bundled assets during runtime.