aio-libs/aiohttp · error · RuntimeError

.url_for() is not supported by sub-application root

Error message

.url_for() is not supported by sub-application root

What it means

PrefixResource (the resource type used as the root of a sub-application added via add_subapp) does not support url_for(), because a sub-app root has no single canonical URL — the sub-app's own resources have URLs, but the mount prefix itself does not. Calling url_for() on it raises RuntimeError to signal this is a logical misuse.

Solutions

  1. Call url_for() on a specific named route inside the sub-app, not on the sub-app root resource.
  2. When iterating resources, skip those whose get_info() indicates a sub-app root (or skip resources that are instances of PrefixResource used for sub-apps).
  3. Use named routes (name=...) and app.router.named_resources() to resolve URLs safely.

Example fix

// before
sub_root = app.router.add_subapp('/api', sub_app)
url = sub_root.url_for()  # RuntimeError
// after
# name a route inside the sub-app and reverse it
sub_app.router.add_get('/users', users_handler, name='users')
url = sub_app.router['users'].url_for()
Defensive patterns

Strategy: type-guard

Validate before calling

from aiohttp.web_urldispatcher import PrefixResource, AbstractResource

def url_for_safe(resource: AbstractResource, *args, **kwargs):
    if isinstance(resource, PrefixResource):
        raise RuntimeError(f'{resource!r} (sub-app root) does not support url_for()')
    return resource.url_for(*args, **kwargs)

Type guard

from aiohttp.web_urldispatcher import PrefixResource

def supports_url_for(resource) -> bool:
    return not isinstance(resource, PrefixResource)

Prevention

When it happens

Trigger: Obtaining the resource for a sub-app mount point (e.g. app.router.add_subapp('/api', sub_app) returns a PrefixResource) and then calling .url_for() on that resource. Code that generically iterates resources and calls url_for() on each will hit this.

Common situations: Generic URL-building helpers that iterate router.resources() and call url_for() without filtering; trying to reverse-generate the sub-app mount prefix; refactoring that accidentally captures the sub-app root instead of a specific route inside it.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/dc5170ebea3bea22. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_urldispatcher.py:729

        super().__init__(prefix)
        self._app = app
        self._add_prefix_to_resources(prefix)

    def add_prefix(self, prefix: str) -> None:
        super().add_prefix(prefix)
        self._add_prefix_to_resources(prefix)

    def _add_prefix_to_resources(self, prefix: str) -> None:
        router = self._app.router
        for resource in router.resources():
            # Since the canonical path of a resource is about
            # to change, we need to unindex it and then reindex
            router.unindex_resource(resource)
            resource.add_prefix(prefix)
            router.index_resource(resource)

    def url_for(self, *args: str, **kwargs: str) -> URL:
        raise RuntimeError(".url_for() is not supported by sub-application root")

    def get_info(self) -> _InfoDict:
        return {"app": self._app, "prefix": self._prefix}

    async def resolve(self, request: Request) -> _Resolve:
        match_info = await self._app.router.resolve(request)
        match_info.add_app(self._app)
        if isinstance(match_info.http_exception, HTTPMethodNotAllowed):
            methods = match_info.http_exception.allowed_methods
        else:
            methods = set()
        return match_info, methods

    def __len__(self) -> int:
        return len(self._app.router.routes())

    def __iter__(self) -> Iterator[AbstractRoute]:
        return iter(self._app.router.routes())

View on GitHub (pinned to d041d4d0fd)