Skip to content

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 app

Grammar

  • appName: lowercase kebab-case, /^[a-z0-9][a-z0-9-]*$/ (per WORKSTREAMS/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:

  1. A route with an origin (development origin-backed route — for example a Vite dev server). Origin proxies always win.
  2. A route with an assetMount (production package-asset-actor route).
  3. 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:

NameTreatment
webThe platform shell. /apps/web/ is the platform-role surface; reserved against being claimed by user-installed packages.
edgeThe 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

FailureResponseCause
Unknown appName404 with error: 'No Host runtime webapp route matched the request'No runtime has registered the appName
Method other than GET/HEAD405 with Allow: GET, HEADgateway.http.request only supports read methods
Asset endpoint returns 404404 from upstream asset actorThe file doesn't exist in dist/browser/
Asset endpoint times out504 (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.