Mounting

Compose reusable plugins and full sub-apps under a URL prefix

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

What mounting is

Mounting composes code from elsewhere under a URL prefix on your app, so one running Chirp app can serve several feature areas — a docs site at/docs, an admin console at /console, your own pages everywhere else.

There are two tools, and they answer different questions:

  • app.mount(prefix, plugin) — you are pulling in a reusable, packaged piece (a docs site, an auth flow, an admin console shipped as a library) that knows how to register itself.
  • app.mount_app(prefix, sub_app) — you have two full Chirp apps and need them on one port during a migration. The sub-app is consumed into the parent: one freeze, one middleware stack, oneapp.check().

Which one do I want?

API Input Reach for it when
mount Any object with aregister(app, prefix)method You ship or consume a reusable, packaged piece.
mount_app Anotherchirp.App You have two full apps and need them on one port, temporarily.

Minimal examples

A plugin is any object with aregister(app, prefix)method. Whatever it registers — routes, middleware, template globals — is persisted on the host app directly. The plugin has no independent lifecycle.

from chirp import App, AppConfig, Request, Template

class DocsPlugin:
    def register(self, app: App, prefix: str) -> None:
        @app.route(f"{prefix}/", name="docs.home")
        async def home(request: Request):
            return Template("docs/home.html")

        @app.route(f"{prefix}/{{page}}")
        async def page(request: Request, page: str):
            return Template("docs/page.html", page=page)


dashboard = App(AppConfig(template_dir="templates"))
dashboard.mount("/docs", DocsPlugin())

mount requires the plugin to expose a callable register. Anything else raises ConfigurationError.

You write two full Chirp apps, then fold one into the other. The sub-app's routes are prefixed and its middleware, hooks, and template globals are hoisted onto the parent.

ConsoleAuthMiddlewarebelow stands in for your own auth middleware.

from chirp import App, AppConfig, Request, Template
from chirp.middleware.sessions import SessionMiddleware

console_app = App(AppConfig(template_dir="console/templates"))

@console_app.route("/", name="console.home")
async def home(request: Request):
    return Template("home.html")

@console_app.route("/users/{user_id:int}")
async def user(request: Request, user_id: int):
    return Template("user.html", user_id=user_id)

console_app.add_middleware(ConsoleAuthMiddleware())

dashboard_app = App(AppConfig(template_dir="dashboard/templates"))

@dashboard_app.route("/")
async def index(request: Request):
    return Template("index.html")

dashboard_app.add_middleware(SessionMiddleware(secret_key="..."))
dashboard_app.mount_app("/console", console_app)
dashboard_app.run()

After mount_app("/console", console_app):

  • /serves the dashboard home.
  • /console serves the console home (url_for("console.home") returns /console).
  • /console/users/{user_id:int}serves the console user detail.
  • For/console/** requests the middleware stack is SessionMiddlewareConsoleAuthMiddleware→ handler (parent middleware wraps the sub-app's).

Whenmount_appcollides

When the sub-app's template globals, filters, providers, error handlers, or severity overrides clash with the parent's, the parent wins. The dropped sub-app entries do not fail silently — each surfaces as an INFO contract issue in categorymount_app_merge when you run app.check(). Promote them to a warning or error withapp.override_contract_severity("mount_app_merge", Severity.WARNING) if a collision should block startup.

mount_appis a migration tool

Once the refactor that motivatedmount_appis done, collapse the sub-app into the parent: move its routes, middleware, and hooks inline. Chainedmount_appcalls work, but the result is harder to reason about than one flat app. If the merge wires up authentication across feature areas, check the secure-by-default middleware stack before you ship.