Skip to content

App mount

mount: is a property of the app block. It names the first path segment every page of the application is served under.

app sports_club "Sampletown Sports Club" {
mount: club
}

The page at route: /members/{id} is then served at /club/members/17.

mount: is optional. An application with no mount: is mounted at its own name, so app sports_club without the declaration is served under /sports_club/.

What a mount decides, and what it does not

Section titled “What a mount decides, and what it does not”

An organization can hold several applications. Two names come out of the app block, and they change at different times.

comes from changes when decides
the mount mount:, defaulting to the application’s name you re-mount the application every address the application serves
the stored table name the application’s name, always <app>__<table> never, after installation what SQLite calls the table

So table members in app sports_club is sports_club__members in the file, whatever the application is mounted at. Re-mounting is free. Renaming the application is a schema migration, because every table, foreign key and generated expression carries the name.

The short name is what you write and what you read. A ref(members), a ctx.db.query("members", …), a compiler error and every line of .mtd all say members.

A mount is one path segment: lower-case letters, digits, _ and -. It starts with a letter and is at most 40 characters long.

mount: club // served at /club/…
mount: club_2026 // served at /club_2026/…
mount: "calendar-2026" // a hyphen needs quotation marks

An unquoted value ends at a hyphen, so mount: calendar-2026 is a parse error. Quote the value to use a hyphen in an address.

mount: / serves the application at the root

Section titled “mount: / serves the application at the root”

One application of an organization can declare the root. Its pages then keep unprefixed addresses, and /members/17 is the member record.

app sports_club "Sampletown Sports Club" {
mount: /
}

Use it for an organization that runs one application and wants no repeated segment in every address, bookmark and calendar subscription.

Three properties follow from the declaration:

  • It is opt-in. No application is at the root unless it says so.
  • An organization has at most one root application. A second one is refused.
  • / is that application’s own address, and there is no list of applications. An organization that already names its front door is not asked again. Other applications of that organization still answer at their own mounts, and no engine page links to them. A root application that declares no page at route: / answers 404 there.

Each message carries the file, the line, the column and the repair.

Declaration Why it is refused
mount: Club a mount is lower case. Two addresses that differ only in case are one address to some clients and two to others
mount: club/events, mount: club.org a mount is one segment, not a path and not a host name
mount: 1club a mount starts with a lower-case letter
a mount over 40 characters somebody types it into a file and reads it out of an address bar

The compiler refuses a mount the engine owns

Section titled “The compiler refuses a mount the engine owns”

The engine owns 15 first path segments, listed in Reserved names. An application mounted at one of them would sit behind a door the engine answers first.

mount "login" is one of the 15 first path segments the engine owns (…) — they belong to the
tenant rather than to any app, so an app mounted at one would never be reached

This applies to a defaulted mount too. app feed with no mount: is refused. The message tells you to declare a mount rather than to rename the application:

an app with no mount: is mounted at its own name, and mount "feed" is one of the 15 first
path segments the engine owns (…)
give the app an address of its own: app feed { mount: <segment> }

The compiler refuses an application name inside mtd_

Section titled “The compiler refuses an application name inside mtd_”

app mtd would store its tables as mtd__members, where the engine keeps the logins, the sessions and the stored model. app mtd_shop is refused for the same reason.

The refusal is about the name, never the mount. An address may be spelled freely and storage may not.

Whether an organization already has a mount is not a compile-time fact, because the compiler never opens a tenant file. matterdata install checks it, and the engine checks it again when it opens the organization.

Collision What the message says
two applications at one mount apps "a" and "b" are both mounted at "club" — one of them would never answer
two applications declaring mount: / a tenant has one root — give all but one of them a mount of their own
a mount that shadows a page of the root application the mount wins, so that page would be unreachable. Mount "b" somewhere else, or take the root away from "a"

The check runs before any table is written, so a refused installation changes nothing:

Nothing was written: the addresses are checked before the tables are. Change `mount:` in
<directory> — an address may move freely, and it renames no table