Nesting Blueprints for Complex Apps
When you build a large Flask application, a flat list of blueprints can become difficult to manage. For example, if you have an API with multiple versions and sub-resources, you might find yourself repeating /api/v1 in every blueprint's url_prefix. Flask allows you to nest blueprints within each other to create a hierarchy that automatically handles shared URL prefixes, subdomains, and endpoint names.
Registering Nested Blueprints
To nest a blueprint, you call the register_blueprint method on another Blueprint instance instead of the Flask application object.
from flask import Blueprint, Flask
app = Flask(__name__)
parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__)
@child.route("/index")
def child_index():
return "Child"
# Register child on parent
parent.register_blueprint(child, url_prefix="/child")
# Register parent on app
app.register_blueprint(parent, url_prefix="/parent")
In this scenario, the route defined in the child blueprint is accessible at /parent/child/index.
URL Prefix Inheritance
Flask handles nested URL prefixes by concatenating them. When Blueprint.register is called on a nested blueprint, it looks at the current url_prefix from the BlueprintSetupState and appends the child's prefix.
The internal implementation in flask.sansio.blueprints.Blueprint.register performs this concatenation:
if state.url_prefix is not None and bp_url_prefix is not None:
bp_options["url_prefix"] = (
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
)
This ensures that even if you have multiple levels of nesting (e.g., parent -> child -> grandchild), the prefixes are joined correctly with single slashes.
Subdomain Nesting
If your application uses subdomains, nested blueprints can also concatenate these. For this to work, you must set subdomain_matching=True on your Flask app and configure a SERVER_NAME.
app.config["SERVER_NAME"] = "example.test"
app.subdomain_matching = True
parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__, subdomain="api")
@child.route("/")
def index():
return "child"
parent.register_blueprint(child)
app.register_blueprint(parent, subdomain="user")
The route will match http://api.user.example.test/. Internally, Flask joins subdomains with a dot:
if state.subdomain is not None and bp_subdomain is not None:
bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
Endpoint Names and url_for
Nesting blueprints affects how you generate URLs with url_for. Flask uses a dotted notation for nested blueprint endpoints. If a blueprint named child is registered on a blueprint named parent, the endpoint for a view named index becomes parent.child.index.
Renaming for Multiple Registrations
You can register the same blueprint multiple times or under different names using the name parameter in register_blueprint. This is useful for versioning or reusing a common set of views.
bp = Blueprint("bp", __name__)
@bp.route("/")
def index():
return "Index"
parent = Blueprint("parent", __name__)
parent.register_blueprint(bp, url_prefix="/v1", name="v1")
parent.register_blueprint(bp, url_prefix="/v2", name="v2")
app.register_blueprint(parent, url_prefix="/api")
The resulting endpoints would be parent.v1.index (at /api/v1/) and parent.v2.index (at /api/v2/).
Internal Mechanics of Nesting
When you call parent.register_blueprint(child), the child blueprint is not immediately registered with the application. Instead, it is added to an internal list self._blueprints in the parent object.
The actual registration happens when the parent is registered on the app. At that point, parent.register(app, options) is called, which iterates through its nested blueprints and recursively calls their register methods:
for blueprint, bp_options in self._blueprints:
# ... calculates prefixes and subdomains ...
bp_options["name_prefix"] = name
blueprint.register(app, bp_options)
During this process, _merge_blueprint_funcs is called to migrate handlers (like before_request and errorhandler) from the blueprint to the application's global structures, prefixing them with the blueprint's dotted name.
Constraints and Requirements
- No Dots in Names: You cannot use a dot in a blueprint's name (e.g.,
Blueprint("my.bp", __name__)will raise aValueError). Dots are reserved as separators for the nesting hierarchy. - No Self-Registration: A blueprint cannot be registered on itself.
- Registration Lock: Once a blueprint has been registered on an application, you can no longer call setup methods like
route,before_request, orregister_blueprinton it. Flask tracks this using the_got_registered_onceflag and will raise anAssertionErrorif you try to modify it. - Record Once Functions: Functions registered with
record_once(such as those adding template filters or globals) are only executed the first time a blueprint is registered, even if it is nested and registered multiple times under different names.