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
- Call url_for() on a specific named route inside the sub-app, not on the sub-app root resource.
- 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).
- 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
- Iterate named resources rather than all resources when building URLs.
- Filter out PrefixResource (sub-app roots) before calling url_for in generic helpers.
- Name the routes inside sub-apps and reverse them via the sub-app's router.
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
- Cannot change apps stack after .freeze() call
- Added route will never be executed, method
- Bad pattern
- Domain cannot be empty
- Domain must be str
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)