Appearance
App routes
The most common URL grammar:
/apps/<appName>/<asset-path>Examples:
/apps/chat/ → loads index.html for chat app
/apps/chat/assets/index.js → loads a chat asset
/apps/director/ → director app
/apps/edge/ → matrix-edge appGrammar
appName: lowercase kebab-case,/^[a-z0-9][a-z0-9-]*$/(perWORKSTREAMS/core-and-packaging/MATRIX-DISCOVERY-METADATA-SPEC.md).asset-path: any normal URL path under the app prefix. Path-traversal attempts are rejected by the asset endpoint.
The appName is reserved against a small set: web, system, api, admin, etc. (per RESERVED_ROUTE_KEYS in projects/matrix-3/packages/system-gateway-http/src/http-routing.ts, though that list is for routeKey paths primarily; appName has its own narrower constraints in the various URL parsers).
Dispatch
In _dispatch (line 179):
typescript
if (isMethod(req.method, 'GET', 'HEAD') && url.pathname.startsWith('/apps/')) {
await this._handlers.onWebapp(request);
return;
}onWebapp is provided by host-http-runtime.ts. It calls SystemGatewayHttpActor.gateway.http.request with pathname and the inferred appName.
Resolution
SystemGatewayHttpActor._resolveRoute (system-gateway-http/src/index.ts:514-543):
typescript
private _resolveRoute(routes, rawPath, requestedAppName) {
const requestUrl = new URL(rawPath, 'http://matrix.local');
const appName = requestedAppName || appNameFromPath(requestUrl.pathname);
if (!appName) return null;
const candidates = routes.filter((route) => route.appName === appName);
const route = candidates.find((c) => c.origin)
?? candidates.find((c) => c.assetMount)
?? candidates[0]
?? null;
if (!route) return null;
const assetPath = assetPathForApp(requestUrl.pathname, appName);
return { routeKind, pathname, appName, assetPath, runtimeId, source, assetMount?, origin? };
}Selection priority within candidates for the same appName:
- A route with an
origin(development origin-backed route — for example a Vite dev server). Origin proxies always win. - A route with an
assetMount(production package-asset-actor route). - Any route at all.
Multiple candidates for the same appName happen when several runtimes register the same webapp — typically a transient state during reconciliation.
Trailing slash
A request for /apps/chat (without trailing slash) is NOT redirected. The match path.startsWith('/apps/') succeeds, and the asset endpoint resolves "chat" as the app name with no asset path. Whether that returns index.html depends on the asset endpoint's SPA fallback logic.
For path-key/public-namespace routes, the parsing functions explicitly note requiresTrailingSlashRedirect: appSlash === -1, but the dispatch path for those routes redirects only if the parser flagged the URL.
Edge alias
/edge and /edge/... redirect to /apps/edge/... via 302, per buildEdgeAliasLocation in http-listener.ts. This is a UX convenience — older bookmarks still work.
Reserved app names
Some app names are special:
| Name | Treatment |
|---|---|
web | The platform shell. /apps/web/ is the platform-role surface; reserved against being claimed by user-installed packages. |
edge | The Device-role shell. /apps/edge/... includes API stubs (/apps/edge/healthz, /apps/edge/api/bootstrap) that translate to local /healthz and /api/bootstrap per routedEdgeApiPath. |
Both of these are first-class platform packages (@open-matrix/matrix-web, @open-matrix/matrix-edge).
Failure modes
| Failure | Response | Cause |
|---|---|---|
Unknown appName | 404 with error: 'No Host runtime webapp route matched the request' | No runtime has registered the appName |
| Method other than GET/HEAD | 405 with Allow: GET, HEAD | gateway.http.request only supports read methods |
| Asset endpoint returns 404 | 404 from upstream asset actor | The file doesn't exist in dist/browser/ |
| Asset endpoint times out | 504 (or whatever default RR timeout produces) | Runtime is unhealthy |
See also
Source:
projects/matrix-3/packages/system-gateway-http/src/http-listener.ts:179-181,projects/matrix-3/packages/system-gateway-http/src/index.ts:514-543.