## 1. Project Overview & Quickstart (marko-js/syntax)

# syntax

Open-source repository marko-js/syntax

### Repository Details
- **Repository:** [marko-js/syntax](https://github.com/marko-js/syntax)
- **Primary Language:** Code

*Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.*

## 2. Official Technical Reference & Guides (marko-js/website)

## File: README.md

# Marko Website

## Overview

This project is powered by [@marko/run](https://github.com/marko-js/run).

- Run `pnpm run dev` to start the development server
- Run `pnpm run build` to build a production-ready node.js server
- Run `pnpm run preview` to run the production server

---

## File: agent-feedback/README.md

# Agent Feedback

Actionable observations that were out of scope for the task that surfaced them. In scope: fix it. Out of scope: file it here. Never expand a task's diff to fix an item recorded here.

One item per file in `items/`, named `YYYY-MM-DD-<slug>.md`.

## When to file

Anything a future contributor should act on:

- `bug`: a suspected defect left unpursued
- `cleanup`: duplication, dead code, inconsistency, refactor opportunity
- `perf`: speed, memory, payload or bundle size, build time
- `dx`: friction in builds, tests, tooling, or repo workflows
- `unclear`: code or docs that were confusing, and what would have clarified them

## Rules

1. **Verify first.** A guess is not feedback. Every item ends with a check that reproduces the claim.
2. **Dedupe first.** `grep -ril '' agent-feedback/items`. If a file covers it, edit that file only when you add new information.
3. **Check the code site.** An intent comment there means the behavior is deliberate. Do not file it.
4. **Self-contained.** Paths, symbols, reasoning. Never reference conversation context or "earlier analysis".
5. **Cite by stable symbol**, never line number.
6. **State the defect and the check.** Never describe what works. Never narrate a landed fix.
7. **Direction is preventive for `unclear` and `dx`.** Name what would have stopped the trip: a comment, a doc line, a lint rule, a compile error, a debug-only warning. The goal is that the next agent does not hit it.
8. **Resolve by deleting the file in the same PR as the fix.** A partial fix rewrites the file to what remains.
9. **Won't-fix is a maintainer's call, never an agent's.** Add a comment (two lines max) at the code site stating the behavior and why it is deliberate, then delete the file. The comment is what stops re-filing. Never consult git history to learn whether something was resolved; if it is not in `items/` and not commented at the site, it is unresolved.

## Item format

`items/YYYY-MM-DD-<slug>.md`:

```md
---
type: bug | cleanup | perf | dx | unclear
impact: high | med | low
effort: high | med | low
site:  › <nearestStableSymbol>
---

# <one-line imperative title>

<2-6 sentences: the problem, why it matters, a concrete direction. Cut evidence a fixer can re-derive from the site.>

Check: <command, input, or observation that reproduces the claim>
```

`impact`: what breaks or is lost if ignored. `effort`: expected size of the fix. Both are the filer's estimate; triage re-judges.

## Repo notes

The markojs.com site: docs as markdown under `docs/`, a `@marko/run` app under `src/`, plus the browser playground. The root `AGENTS.md` holds the documentation writing guidelines and they govern any docs change; read it too.

**Reproduce a claim.**

- Docs content: `grep` the page, then verify the behavior it describes against Marko runtime source, a fixture snapshot, or a compiled probe. An existing docs sentence is not evidence.
- Anchors and slugs: `github-slugger` resolves collisions in document order, so a second heading with the same slug becomes `#name-1`. Check with the slugger directly rather than guessing.
- Playground and site behavior: `pnpm run dev`, then reproduce in the browser.
- Build-derived output (page `<meta>`, generated llms bundles): `pnpm run build`, then read `src/routes/docs/_compiled-docs/`.

**Guard tests.** `pnpm test` (`vitest run`). A docs-structure claim (every page indexed, every anchor resolving) is best guarded by a test that walks `docs/**/*.md`, not by a snapshot of one page.

**Pre-ship.** `pnpm run lint` (markdownlint over `docs/**/*.md`, then cspell) and `pnpm run type-check` (`marko-type-check`). `pnpm run build` when the change can affect generated output.

**Gotchas.** `pnpm run lint` runs cspell, which has a Node engine floor above some default installs and aborts on the version check before checking anything; the husky pre-commit hook hits the same wall. Never document a known Marko bug as intended behavior: file the defect instead. Never use an em dash in docs, and grep `' - '` too, since a spaced hyphen doing the same job is the same problem.

---

## File: docs/marko-run/adapters.md

# Adapters

Adapters change the development, build, and preview process to fit different deployment platforms and runtimes. Application code stays written in [web standard APIs](./runtime.md#context). By swapping the adapter, the same routes can be served by a Node process, deployed to an edge platform, or rendered to static files.

## Choosing an Adapter

[Marko Run's Vite plugin](./vite-plugin.md) resolves the adapter in this order:

1. The `adapter` specified in the plugin options
2. The first dependency in `package.json` whose name starts with `@marko/run-adapter` or contains `marko-run-adapter`
3. The default Node-based adapter that ships with `@marko/run`

For most applications, installing an adapter package is all that is needed. To configure an adapter (or choose between several installed ones), pass it to the plugin explicitly:

```ts
import { defineConfig } from "vite";
import marko from "@marko/run/vite";
import netlify from "@marko/run-adapter-netlify"; // Import the adapter

export default defineConfig({
  plugins: [
    marko({
      adapter: netlify({ edge: true }), // Configure and apply the adapter
    }),
  ],
});
```

## Node

[`@marko/run-adapter-node`](https://github.com/marko-js/run/tree/main/packages/adapters/node) previews and deploys apps on Connect-style servers such as Express.

```sh
npm install @marko/run-adapter-node
```

Beyond building a standalone server, this adapter provides middleware that converts Connect-style requests into [WHATWG requests](https://developer.mozilla.org/en-US/docs/Web/API/Request) and writes [WHATWG responses](https://developer.mozilla.org/en-US/docs/Web/API/Response) back. This lets an application mount its Marko Run routes inside an existing server.

The **router middleware** fully handles requests that match a route:

```ts
import express from "express";
import { routerMiddleware } from "@marko/run-adapter-node/middleware";

express()
  .use("/assets", express.static("assets"))
  .use(routerMiddleware()) // register the router middleware
  .listen(8080);
```

The **match middleware** instead attaches the matched route onto the request object, where it can be invoked later. This is useful when other middleware needs to run between finding a match and invoking the route:

```ts
import express from "express";
import { matchMiddleware } from "@marko/run-adapter-node/middleware";

express()
  .use("/assets", express.static("assets"))
  .use(matchMiddleware()) // register the match middleware
  .use((req, res, next) => {
    if (req.route) {
      // `req.route.config` contains the route's metadata
    }
    next();
  })
  .use((req, res, next) => {
    if (req.route) {
      req.route.invoke(req, res, next); // finally invoke the route
    } else {
      next();
    }
  })
  .listen(8080);
```

## Static

[`@marko/run-adapter-static`](https://github.com/marko-js/run/tree/main/packages/adapters/static) builds the application into static files that can be served from any file server or CDN.

```sh
npm install @marko/run-adapter-static
```

At build time, the adapter renders every statically known route and crawls relative anchor links in the rendered pages to discover more. The `urls` option provides additional entry points beyond those discovered automatically:

```ts
import { defineConfig } from "vite";
import marko from "@marko/run/vite";
import staticAdapter from "@marko/run-adapter-static";

export default defineConfig({
  plugins: [
    marko({
      adapter: staticAdapter({
        urls: ["/products/lamp", "/products/chair"],
      }),
    }),
  ],
});
```

> [!NOTE]
> The `urls` option can also be an async function that receives the discovered routes and returns URL strings.

## Netlify

[`@marko/run-adapter-netlify`](https://github.com/marko-js/run/tree/main/packages/adapters/netlify) deploys apps to Netlify Functions or Edge Functions.

```sh
npm install @marko/run-adapter-netlify
```

Netlify applications require a [`netlify.toml`](https://docs.netlify.com/configure-builds/file-based-configuration/) configuration. This example is a starting point for apps deployed to Netlify Functions:

```toml
[build]
  command = "marko-run build"
  publish = "dist/public"
  functions = "dist"
```

To build for [Edge Functions](https://docs.netlify.com/edge-functions/overview/) instead, configure the adapter with `edge: true`:

```ts
import { defineConfig } from "vite";
import marko from "@marko/run/vite";
import netlifyAdapter from "@marko/run-adapter-netlify";

export default defineConfig({
  plugins: [
    marko({
      adapter: netlifyAdapter({ edge: true }),
    }),
  ],
});
```

and specify the `edge_functions` directory in `netlify.toml` instead of `functions`:

```toml
[build]
  command = "marko-run build"
  publish = "dist/public"
  edge_functions = "dist"
```

## Custom Adapters

An adapter is an object implementing the `Adapter` interface from `@marko/run/vite`, with hooks into the dev server, build, and preview lifecycle. The [official adapters](https://github.com/marko-js/run/tree/main/packages/adapters) serve as reference implementations.

## Next Steps

- [Vite Plugin](./vite-plugin.md)
- [Runtime](./runtime.md#embedding)
- [CLI](./cli.md)

---

## File: docs/marko-run/cli.md

# CLI

The `marko-run` CLI develops, builds, and previews Marko Run applications. It is installed with `@marko/run` and run with `npm exec marko-run` or from package.json scripts.

The CLI has three commands: `dev`, `build`, and `preview`. All commands accept these options:

| Option           | Description                                                                                                                      |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `-c`, `--config` | Path to a Vite config file (by default, looks for a `vite.config` file with a `.js`, `.cjs`, `.mjs`, `.ts`, or `.mts` extension) |
| `-e`, `--env`    | Path to a dotenv file                                                                                                            |

## `dev`

Starts a development server in watch mode. This is the default command, so both of the following are equivalent:

```sh
npm exec marko-run
npm exec marko-run dev
```

| Option         | Description                                                                                                          |
| -------------- | -------------------------------------------------------------------------------------------------------------------- |
| `-p`, `--port` | Port to listen on (defaults: `server.port` then `preview.port` in the Vite config, the `PORT` env variable, or 3000) |

## `build`

Creates a production build.

```sh
npm exec marko-run build
```

| Option           | Description                                                                 |
| ---------------- | --------------------------------------------------------------------------- |
| `-o`, `--output` | Directory to write built files (default: `build.outDir` in the Vite config) |

## `preview`

Creates a production build and starts a production-like server.

```sh
npm exec marko-run preview
```

| Option           | Description                                                                                                                         |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `-o`, `--output` | Directory to serve files from, and write built files to (default: `build.outDir` in the Vite config)                                |
| `-p`, `--port`   | Port the server should listen on (defaults: `preview.port` then `server.port` in the Vite config, the `PORT` env variable, or 3000) |
| `-f`, `--file`   | Output file to start                                                                                                                |

## Next Steps

- [Vite Plugin](./vite-plugin.md)
- [Adapters](./adapters.md)

---

## File: docs/marko-run/data-loading.md

# Data Loading

Handlers and middleware pass data to everything downstream, including pages and layouts, by calling `next` with an object. This is the primary way a page gets data that requires server-side work: fetching from a database, reading a session, or calling another service.

## Passing Data

Data passed to `next` is shallowly merged into `ctx.data`, which templates access as `$global.data`:

```ts
/* +handler.ts */
export const GET = Run.GET((ctx, next) => {
  return next({ user: loadUser(ctx.params.id) }); // pass the promise itself
});
```

```marko
/* +page.marko */
<await|user|=$global.data.user>
  <h1>Hello ${user.name}</h1>
</await>
```

Each middleware and handler in the chain contributes to the same object. A root middleware can load data shared by every page, like the current session. Handlers then add data specific to their route.

Note that `ctx.data` is populated per route and verb. With [generated types](./typescript.md#generated-types), each file sees the routes and verbs it actually participates in, so the type of `$global.data` in a page or layout reflects exactly the middleware and handlers that run before it.

> [!WARNING]
> The merge is shallow. A handler that passes `{ user: ... }` replaces any `user` value provided upstream instead of merging into it.

## Promises over Awaiting

Notice that the handler above does not `await` the data. Passing the promise and unwrapping it in the template with [`<await>`](../reference/core-tag.md#await) lets the response start [streaming](../explanation/streaming.md) immediately. Static markup, the document `<head>`, and anything else that does not depend on the data reach the browser while the data is still loading. Each `<await>` section then flushes as its promise resolves.

Independent data sources are best kept as separate promises. Two `<await>` sections resolve independently, while awaiting both in a handler serializes them behind one another.

> [!WARNING]
> Avoid awaiting data in a `GET` handler before calling `next`. Every `await` before `next` holds back the entire response until that data resolves. This forfeits streaming and delays even the parts of the page that never needed the data. Pass the promise through `next` and let the template await it instead.

## Rendering and Responses

The `next` function renders the page for `GET`, `POST`, and `QUERY` requests where applicable, returns a `200` response for `HEAD` requests, or a `204` response when there is no page. A handler decides what to do with that result:

```ts
export const GET = Run.GET((ctx, next) => {
  if (!ctx.url.searchParams.has("beta")) {
    return ctx.redirect("/request-access"); // never renders the page
  }
  return next({ features: loadBetaFeatures() });
});
```

> [!NOTE]
> A handler that always returns its own `Response` never renders the page, so its verb is excluded from the template's typed context.

## Forms

When a route has both a page and a `POST` handler, the handler calling `next` renders the page, so form submissions can be answered with HTML directly. A `POST` handler has two ways to respond, and they serve different purposes:

- On success, when the user should move to a new screen or experience, respond with a [redirect](./runtime.md#redirect). This is the [Post/Redirect/Get](https://en.wikipedia.org/wiki/Post/Redirect/Get) pattern. The browser lands on an ordinary `GET` page, so refreshing does not resubmit the form and the URL can be shared or revisited safely.
- Return `next()` to render the page from the `POST` itself. This is useful for rendering validation errors while keeping the submitted form intact, or for an ephemeral page that has no meaning outside this submission.

```ts
/* +handler.ts */
export const POST = Run.POST(
  { form: { validator: parseSignup } },
  async (ctx, next) => {
    const [signup, issues] = await ctx.body;
    if (issues) {
      return next({ issues }); // re-render the form with errors
    }
    await saveSignup(signup);
    return ctx.redirect("/thanks"); // Post/Redirect/Get on success
  },
);
```

```marko
/* +page.marko */
<form method="post">
  
    Please correct the highlighted fields.
  
  
  Sign up
</form>
```

> [!WARNING]
> A page rendered by returning `next()` from a `POST` is still a `POST` response. Refreshing it prompts the browser to resubmit the request, and the URL cannot be bookmarked or shared. Reserve it for error re-renders and ephemeral pages. Redirect whenever the submission succeeds and the user moves on.

## Next Steps

- [Validation](./validation.md#request-bodies)
- [Runtime](./runtime.md#context-methods)
- [Streaming](../explanation/streaming.md)

---

## File: docs/marko-run/file-based-routing.md

# File-based Routing

Marko Run discovers routes from the file system. The directory structure under the **routes directory**, `./src/routes`, determines the paths an application serves. A small set of `+` prefixed file names determines what happens at each path.

> [!NOTE]
> The `+` prefix makes routable files explicit. Anything without the prefix is ignored by the router, so components, tests, stories, and utilities can be colocated alongside the pages that use them without accidentally being served.

## Routable Files

The router only recognizes certain filenames, all prefixed with `+`. The following filenames will be discovered in any directory inside the application's routes directory.

### `+page.marko`

These files establish a route at the current directory path, which will be served for `GET` requests with the HTML content of the page. Only one page may exist for any served path.

### `+layout.marko`

These files provide a **layout component**, which will wrap all nested layouts and pages.

Layouts are like any other Marko component, with no extra constraints. Each layout receives a `content` input which refers to the nested page or layout being rendered. The [request context](./runtime.md#context) is available as [`$global`](../reference/language.md#global).

```marko
export interface Input {
  content: Marko.Body;
}

<main>
  <h1>My Products</h1>

  // render the nested page or layout here
  <${input.content}/>
</main>
```

### `+handler.*`

These files establish a route at the current directory path that can respond to any HTTP method: exported functions named `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`, or `QUERY` handle requests with the matching method.

Typically, these will be `.js` or `.ts` files, depending on the project. Like pages, only one handler file may exist for any served path.

Handlers are created with the global [verb helpers](./validation.md#verb-helpers) (`Run.GET`, `Run.POST`, etc.), which add request validation, typed bodies, and [data loading](./data-loading.md):

```ts
export const POST = Run.POST({ json: ReminderSchema }, async (ctx) => {
  const [reminder, issues] = await ctx.body; // parsed and validated by the `json` option
  if (issues) {
    return Response.json({ issues }, { status: 422 });
  }
  await saveReminder(ctx.params.listId, reminder);
  return Response.json(reminder, { status: 201 });
});
```

Handler functions are synchronous or asynchronous functions that receive two arguments:

- `ctx` contains the WHATWG request object, path parameters, URL, route metadata, and the [validated body](./validation.md#request-bodies) (see [Context](./runtime.md#context))
- `next` renders the page for `GET`, `POST`, and `QUERY` requests where applicable, a `200` response for `HEAD` requests, or a `204` response for all other verbs. Pass it an object to [make data available](./data-loading.md) to downstream handlers and the page.

> [!TIP]
> Configure the [`json` or `form` option](./validation.md#request-bodies) to read validated request body content with `await ctx.body`. Calling `ctx.request.json()` or `ctx.request.formData()` in a handler bypasses validation and any configured size limits. Using [`Response.json`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json_static) makes it easy to return JSON encoded data.

A handler function may return (or throw) a WHATWG response, or return `undefined`. If the function returns `undefined`, `next` will be automatically called and used as the response.

```ts
export const GET = Run.GET(async (ctx, next) => {
  // do something before rendering the page
  const response = await next();
  // do something with the response
  return response;
});

export const PUT = Run.PUT((ctx, next) => {
  // `next` will be called automatically since nothing is returned
});

export const DELETE = Run.DELETE((ctx, next) => {
  return new Response("Successfully removed", { status: 204 });
});
```

> [!NOTE]
> When a route has no `HEAD` export, `HEAD` requests are served by the `GET` handler (or page) with the response body stripped.

### `+middleware.*`

These files are like layouts, but for handlers. Middleware files are called before handlers and allow work to be performed before and after them.

Middleware files expect a `default` export with the same shapes as handler exports: a handler function, an array of handler functions, or a promise resolving to either. Middleware is created with `Run.ALL` or a specific verb helper. It may also be options-only, e.g. `Run.ALL({ search: ... })`, to attach [validation](./validation.md) for every route below it without adding behavior.

```ts
export default Run.ALL(async (ctx, next) => {
  const requestName = `${ctx.request.method} ${ctx.url.href}`;
  let success = true;
  console.log(`${requestName} request started`);
  try {
    return await next(); // Wait for subsequent middleware, handler, and page
  } catch (err) {
    success = false;
    throw err;
  } finally {
    console.log(
      `${requestName} completed ${success ? "successfully" : "with errors"}`,
    );
  }
});
```

> [!NOTE]
> Unlike handlers, middleware runs for all HTTP methods. Middleware created with `Run.ALL(...)` runs for every method, while middleware created with a specific verb helper (e.g. `Run.POST(...)`) is skipped for other methods.

### `+meta.*`

These files represent static metadata to attach to the route. This metadata will be automatically provided as `ctx.meta` when the route is invoked. When the file is a non-JSON file, its default export will be used.

Metadata supports verb-specific overrides when it is an object (e.g. a JSON file or `export default { ... }`). Top-level keys that match one of `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`, or `QUERY` are shallowly merged into the base object for requests of that method, overriding any existing values. These keys are excluded from the base object, and ignored when their value is not an object. For example, given a `+meta.json` file:

```json
{
  "name": "Default Name",
  "POST": {
    "name": "Post Name",
    "postOnlyData": "foo"
  },
  "GET": {
    "name": "Get Name"
  },
  "PUT": "Ignored"
}
```

for a `POST` request, the value of `ctx.meta` will be

```js
{
  name: "Post Name",
  postOnlyData: "foo"
}
```

for a `GET` request, it will be

```js
{
  name: "Get Name"
}
```

and for all other methods, including `PUT`, it will be the base object

```js
{
  name: "Default Name"
}
```

## Special Files

In addition to the files above, which can be defined in any directory under the routes directory, some special files can only be defined at its top level.

These special pages are subject to a root layout file (`src/routes/+layout.marko` in the default configuration).

### `+404.marko`

This special page responds to any request where:

- The `Accept` request header includes `text/html`
- _And_ no other handler or page rendered the request

Responses with this page will have a `404` status code.

### `+500.marko`

This special page responds to any request where:

- The `Accept` request header includes `text/html`
- _And_ an uncaught error occurs while serving the request

Responses with this page will have a `500` status code.

## Execution Order

Given the following routes directory structure

```text
routes/
  about/
    +handler.js
    +layout.marko
    +middleware.js
    +page.marko
  +layout.marko
  +middleware.js
  +page.marko
```

When the path `/about` is requested, the routable files execute in the following order:

1. Middlewares from root-most to leaf-most
2. Handler
3. Layouts from root-most to leaf-most
4. Page

> [!NOTE]
> Layouts and the page are combined at build time into a single component, so nested layouts add no runtime overhead.

## Path Structure

Within the routes directory, the directory structure determines the path from which the route is served. There are four types of directory names: **static**, **pathless**, **dynamic**, and **catch-all**.

### Static Directories

The most common type, and the default behavior. Each static directory contributes its name as a segment in the route's served path, similar to a traditional file server. Unless a directory name matches the requirements for one of the below types, it is seen as a static directory.

Examples:

```text
/foo
/users
/projects
```

### Pathless Directories

These directories do **not** contribute their name to the route's served path. Directory names that start with an underscore (`_`) will be ignored when parsing the route.

Examples:

```text
/_users
/_public
```

### Dynamic Directories

These directories introduce a dynamic parameter to the route's served path and will match any value at that segment. Any directory name that starts with a single dollar sign (`$`) will be a dynamic directory, and the remaining directory name will be the parameter at runtime. If the directory name is exactly `$`, the parameter will not be captured, but it will be matched.

Examples:

```text
/$id
/$name
/$
```

### Catch-all Directories

These directories are similar to dynamic directories and introduce a dynamic parameter, but instead of matching a single path segment, they match to the end of the path. Any directory that starts with two dollar signs (`$$`) will be a catch-all directory, and the remaining directory name will be the parameter at runtime. In the case of a directory named `$$`, the parameter name will not be captured, but it will match. Catch-all directories can be used to make `404` Not Found routes at any level, including the root.

Because catch-all directories match any path segment and consume the rest of the path, route files cannot be nested inside them, and no further directories will be traversed.

Examples:

```text
/$$all
/$$rest
/$$
```

## Flat Routes

Flat routes define paths without needing additional directories. Instead, the directory structure can be defined either in the file or directory name. This allows routes to be decoupled from the directory structure or co-located as needed. To define a flat route, use periods (`.`) to delineate each path segment. This behaves exactly like creating a new directory, and each segment will be parsed using the rules described above for static, dynamic, and pathless routes.

Flat route syntax can be used for both directories and routable files (e.g. pages, handlers, middleware). For these files, anything preceding the plus (`+`) will be treated as the flat route.

For example, to define a page at `/projects/$projectId/members` with a root layout and a project layout:

Without flat routes, the file structure would look like:

```text
routes/
  projects/
    $projectId/
      members/
        +page.marko
      +layout.marko
  +layout.marko
```

With flat routes, move the path defined by the directories into the files and separate with a period

```text
routes/
  +layout.marko
  projects.$projectId+layout.marko
  projects.$projectId.members+page.marko
```

Additionally, files can still be organized under directories to decrease duplication, with flat route syntax in the directory name

```text
routes/
  projects.$projectId/
    +layout.marko
    members+page.marko
  +layout.marko
```

Finally, flat routes and routes defined with directories are all treated equally and merged together. For example, this page will have the layout defined in the nested directories:

```text
routes/
  projects/
    $projectId/
      +layout.marko
  projects.$projectId+page.marko
```

### Multiple Paths

Along with describing multiple segments, flat route syntax supports defining routes that match more than one path. To describe a route that matches multiple paths, use a comma (`,`) and define each route.

For example the following page matches `/projects/$projectId/members` and `/projects/$projectId/people`

```text
routes/
  projects.$projectId.members,projects.$projectId.people+page.marko
```

This file name is a bit long, so a directory can shoulder the shared prefix

```text
routes/
  projects.$projectId/
    members,people+page.marko
```

### Groups

**Grouping** simplifies multiple paths further. Groups define segments within a flat route that match multiple sub-paths by surrounding them with parentheses (`(` and `)`). With a group, the page above collapses back into a single file:

```text
routes/
  projects.$projectId.(members,people)+page.marko
```

This is a basic use of grouping, but groups can be nested and combined as needed.

### Optional Segments

Introducing an empty segment or pathless segment along with another value makes that segment optional. For example, a page that matches both `/projects` and `/projects/home` can be created with a flat route that optionally matches `home`

```text
routes/
  projects.(home,)+page.marko
```

or

```text
routes/
  projects.(home,_pathless)+page.marko
```

While both of these create a route which matches the paths, they have slightly different semantics. Using a pathless segment is the same as creating a pathless directory, which allows middleware and layouts to be isolated. Using an empty segment is the same as defining a file at the current location.

## Escaping Characters

To include a control character (`.`, `,`, `+`, `(`, `)`, `$`, `_`) as a literal in a route path, surround it with backticks (`` ` ``). For example, to create a route served at `/sitemap.xml`:

```text
routes/
  `sitemap.xml`/
    +handler.ts
```

or

```text
routes/
  sitemap`.`xml/
    +handler.ts
```

or

```text
routes/
  sitemap`.`xml+handler.ts
```

## Next Steps

- [TypeScript](./typescript.md)
- [Validation](./validation.md)
- [Data Loading](./data-loading.md)

---

## File: docs/marko-run/getting-started.md

# Getting Started

Marko Run ([`@marko/run`](https://github.com/marko-js/run)) is the application framework for Marko. It turns a directory of Marko templates into a full web application with [file-based routing](./file-based-routing.md), nested layouts, middleware, streaming server-side rendering, and [adapters](./adapters.md) for deploying to different platforms.

Marko Run is powered by [Vite](https://vitejs.dev/) and works with zero configuration. The package ships with a default Vite config and a Node-based adapter, so a single `+page.marko` file is enough to serve an application.

## Using a Template

Marko provides project templates through `npm init marko`, many of which use Marko Run.

```sh
npm init marko
```

After choosing a template and project name:

```sh
cd PROJECT_NAME
npm run dev
```

## Manual Setup

Marko Run requires only one dependency in addition to `marko`, whether starting fresh or adding it to an existing Marko project.

1. Install the package

   ```sh
   npm install @marko/run
   ```

2. Create the first page at `src/routes/+page.marko`

   ```marko
   <h1>Hello Marko Run</h1>
   ```

3. Start the development server with the [CLI](./cli.md)

   ```sh
   npm exec marko-run
   ```

The application is now available at `http://localhost:3000` 🚀

> [!NOTE]
> No Vite config file is required, and [adapters](./adapters.md) are discovered automatically by package name. When a config file is needed, for example to set plugin options or register additional Vite plugins, see [Marko Run's Vite plugin](./vite-plugin.md).

## Next Steps

- [File-based Routing](./file-based-routing.md)
- [TypeScript](./typescript.md)
- [Adapters](./adapters.md)

---

## File: docs/marko-run/runtime.md

# Runtime

At build time, Marko Run generates a router from the application's [route files](./file-based-routing.md). For each request, the router creates a **context** object that flows through middleware, handlers, and templates. When using an [adapter](./adapters.md), the runtime is abstracted away. It can also be [embedded](#embedding) in an existing server.

## Context

The context is passed to middleware and handler functions as the first parameter, conventionally named `ctx`. In Marko templates, it is available as [`$global`](../reference/language.md#global). It contains information about the current request:

| Property   | Description                                                                                                                                      |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `route`    | A string identifying the current route's path pattern                                                                                            |
| `request`  | The current [WHATWG `Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) instance                                                |
| `method`   | HTTP method of the current request                                                                                                               |
| `url`      | [WHATWG `URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instance of the current request                                             |
| `params`   | The route's path parameters, transformed by any [`params` validators](./validation.md#params-and-search)                                         |
| `search`   | The request's query string values, transformed by any [`search` validators](./validation.md#params-and-search)                                   |
| `body`     | Promise for the parsed request body when the route configures a [`json` or `form` option](./validation.md#request-bodies), otherwise `undefined` |
| `data`     | Data passed from upstream middleware and handlers via [`next(data)`](./data-loading.md)                                                          |
| `meta`     | Metadata loaded from the current route's [`+meta` file](./file-based-routing.md#meta)                                                            |
| `platform` | Additional data provided by the [adapter](./adapters.md)                                                                                         |
| `parent`   | The calling context when the request was made with [`ctx.fetch`](#fetch), otherwise `undefined`                                                  |

> [!NOTE]
> In templates, `$global.params` and `$global.url` are serialized to the browser by default, so client-side code can read them after hydration. Other context properties can be included by setting them in `ctx.serializedGlobals`, which is the same list described under [`$global.serializedGlobals`](../reference/template.md#globalserializedglobals).

## Context Methods

The context also provides helpers for producing responses.

### `fetch`

```ts
fetch(resource: string | URL | Request, init?: RequestInit): Promise<Response>;
```

Creates a response by making a new request to the router. This method has the same signature as native [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch). Relative URLs resolve against the current request's URL, and the new request's context has this context as its `parent`.

### `render`

```ts
render<T>(template: Marko.Template<T>, input: T, init?: ResponseInit): Response;
```

Creates a response that streams the given Marko template and sets the `Content-Type` header to `text/html`.

### `redirect`

```ts
redirect(to: string | URL, status?: number): Response;
```

Creates a redirect response, resolving relative paths against the current URL. The status defaults to `302` and must be one of `301`, `302`, `303`, `307`, or `308`.

### `back`

```ts
back(fallback?: string | URL, status?: number): Response;
```

Creates a redirect response that uses the current request's `Referer` header, or the fallback (default `/`) when there is none.

## Typed URLs

The runtime also provides `Run.href`, which builds URLs for the application's routes with type checking of the path, params, search, and hash. The path is typed against the routes the application actually serves. Renaming or moving a route turns every stale link into a compile-time error instead of a broken `<a>` tag.

```marko
<a href=Run.href("/projects/$projectId/members", {
  params: { projectId: 42 },
  search: { sort: "name" },
  hash: "top",
})>Members</a>
```

| Option   | Description                                                                                                                                                                             |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `params` | Values for the path's [dynamic segments](./file-based-routing.md#path-structure). Required exactly when the path has them. Catch-all (`$$`) params also accept arrays, joined with `/`. |
| `search` | Query string values. When the route declares a [`search` validator](./validation.md#params-and-search), the keys are typed against it.                                                  |
| `hash`   | Fragment appended after `#`.                                                                                                                                                            |

All params, search values, and the hash are URI-encoded automatically.

The full path checking relies on [generated types](./typescript.md#generated-types), so a TSConfig file must be present in the project root for `Run.href` to validate paths and params at compile time. The function still builds correct URLs without it.

> [!NOTE]
> In client builds, Marko Run rewrites statically analyzable `Run.href(...)` calls at compile time, so most calls cost nothing at runtime.

## Embedding

When more control is needed than an [adapter](./adapters.md) provides, the router can be imported directly and embedded in an existing server:

```ts
import * as router from "@marko/run/router";
```

### `router.fetch`

```ts
function fetch(request: Request, platform: unknown): Promise<Response | void>;
```

This asynchronous function takes a [WHATWG `Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and an object containing any platform-specific data made available as `ctx.platform`. It returns any of

- a [WHATWG `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) generated from executing the matched route files
- `undefined` if the request was not explicitly handled
- a `404` status response if no route matches the requested path
- a `500` status response if an uncaught error occurs

Express example:

```ts
import express from "express";
import * as router from "@marko/run/router";

express()
  .use(async (req, res, next) => {
    const request = createWHATWGRequest(req); // code to create a WHATWG Request from `req`

    const response = await router.fetch(request, { req, res });

    if (response) {
      applyResponse(response, res); // code to apply a WHATWG Response to `res`
    } else {
      next();
    }
  })
  .listen(3000);
```

> [!TIP]
> When targeting Node.js servers, the [Node adapter's middleware](./adapters.md#node) already performs this request and response conversion for Connect-style servers like Express.

### `router.match`

```ts
function match(method: string, pathname: string): Route | null;
```

This synchronous function takes an HTTP method and path name. It returns an object representing the best matching route (`params` and `meta`), or `null` if no route matches. This is useful when other parts of a server need to know whether a route exists before creating a response.

### `router.invoke`

```ts
function invoke(
  route: Route,
  request: Request,
  platform: unknown,
): Promise<Response | void>;
```

This asynchronous function takes a route object returned by [`router.match`](#routermatch), along with the request and platform data. It produces a response in the same way [`router.fetch`](#routerfetch) does. Together, `match` and `invoke` split `fetch` into its two steps so that other middleware can run in between:

```ts
import express from "express";
import * as router from "@marko/run/router";

express()
  .use((req, res, next) => {
    res.locals.match = router.match(req.method, req.path);
    next();
  })

  // ...other middleware which can check `res.locals.match`

  .use(async (req, res, next) => {
    if (!res.locals.match) {
      next();
      return;
    }

    const request = createWHATWGRequest(req); // code to create a WHATWG Request from `req`
    const response = await router.invoke(res.locals.match, request, { req, res });

    if (response) {
      applyResponse(response, res); // code to apply a WHATWG Response to `res`
    } else {
      next();
    }
  })
  .listen(3000);
```

## Next Steps

- [Vite Plugin](./vite-plugin.md)
- [Adapters](./adapters.md)
- [TypeScript](./typescript.md)

---

## File: docs/marko-run/typescript.md

# TypeScript

Marko Run is designed to be used with TypeScript. Route files get typed request contexts. Validated params and bodies flow through to pages, and URLs are checked against the routes the application serves.

## Run Namespace

Marko Run provides a global namespace `Run`, available in route files without any imports. At runtime it exposes the [verb helpers](./validation.md#verb-helpers) (`Run.GET`, `Run.POST`, ..., `Run.ALL`) and [`Run.href`](./runtime.md#typed-urls). In TypeScript it also provides:

**`Run.Context`** - Type of the request context object in a handler and `$global` in Marko files. This type can be extended using TypeScript's module and interface merging by declaring a `Context` interface on the `@marko/run` module within the application code:

```ts
declare module "@marko/run" {
  interface Context {
    customProperty: MyCustomThing; // will be globally defined on Run.Context
  }
}
```

The platform object provided by the [adapter](./adapters.md) in use can be typed the same way by declaring a `Platform` interface:

```ts
declare module "@marko/run" {
  interface Platform {
    customProperty: MyCustomThing; // will be available on `ctx.platform`
  }
}
```

> [!NOTE]
> Reserve the `Context` interface for application-wide properties that every route can rely on, like a database connection or session helper. For route-specific values, prefer [data loading](./data-loading.md) via `next`, which types the data per route automatically.

## Generated Types

If a [TSConfig](https://www.typescriptlang.org/tsconfig) file is discovered in the project root, Marko Run will automatically generate a `.d.ts` file which provides more specific types for each middleware, handler, layout, and page. This file is generated at `.marko-run/routes.d.ts` whenever the project is built, including during dev.

> [!NOTE]
> TypeScript will not include this file by default. Use the [Marko VSCode plugin](https://marketplace.visualstudio.com/items?itemName=Marko-JS.marko-vscode) and [add the file in the tsconfig `include`](https://www.typescriptlang.org/tsconfig#include).

With the generated types, the `Run` namespace is narrowed per routable file:

**`Run.Context`**

- Has the exact path params, meta, validated `search` and `body`, and upstream `data` types for the routes the file serves
- In middleware and layouts which are used in many routes, this type will be a union of all possible routes that the file will see
- When an adapter is used, it can provide types for the platform

**`Run` verb helpers**

Validation options and handler return types are fully inferred, so `ctx.params`, `ctx.search`, `ctx.body`, and the data passed to `next(...)` flow through to downstream handlers, pages, and layouts.

**`Run.href`**

The path and its params are typed against every route the application serves.

## Next Steps

- [Validation](./validation.md)
- [Runtime](./runtime.md#typed-urls)
- [TypeScript in Marko](../reference/typescript.md)

---

## File: docs/marko-run/validation.md

# Validation

Handlers and middleware are written using the verb helpers defined on the global `Run` namespace. Beyond declaring which HTTP method a handler responds to, these helpers accept validation options that check, transform, and type a route's path parameters, query string, and request body.

## Verb Helpers

Each verb helper (`Run.GET`, `Run.HEAD`, `Run.POST`, `Run.PUT`, `Run.DELETE`, `Run.PATCH`, `Run.OPTIONS`, `Run.QUERY`, and `Run.ALL`) accepts a handler function or an array of handler functions, optionally preceded by validation options:

```ts
Run.POST(handler); // a handler function
Run.POST([auth, handler]); // an array of handlers, composed in order
Run.POST(options, handler); // validation options plus a handler
Run.POST(options, [auth, handler]); // validation options plus an array of handlers
Run.POST(options); // options only, validates and passes through
```

In a [`+handler` file](./file-based-routing.md#handler), export the result under the matching verb name. In a [`+middleware` file](./file-based-routing.md#middleware), use the default export. Handlers created for a specific verb only run for requests with that method. `Run.ALL`, available in middleware, runs for every method.

> [!NOTE]
> The `Run` namespace is a global, so no imports are needed in route files. See [TypeScript](./typescript.md#run-namespace) for the types it provides.

## Params and Search

The `params` and `search` options validate, and can transform, the route's [path parameters](./file-based-routing.md#path-structure) and query string. A validator is either a function or a [Standard Schema](https://github.com/standard-schema/standard-schema) (Zod, Valibot, ArkType, etc.).

Consider a middleware that normalizes a `page` query parameter for every route below it, and a handler that converts its `id` path parameter to a number:

```ts
/* +middleware.ts */
export default Run.ALL({
  search(value) {
    return { ...value, page: Number(value.page) || 0 };
  },
});
```

```ts
/* $id/+handler.ts */
export const GET = Run.GET(
  {
    params(value) {
      return { id: Number(value.id) };
    },
  },
  (ctx) => {
    ctx.params.id; // number
    ctx.search.page; // number
  },
);
```

Function validators replace the value with whatever they return. Standard Schema validators produce a [`[value, issues]` tuple](#standard-schema).

Validators are lazy. `ctx.params` and `ctx.search` do not run their validators until the property is accessed. The first access runs the validator and caches the result for later reads. A request that never touches these properties never pays for their validation.

> [!WARNING]
> Standard Schema validation must be synchronous. Passing a schema whose validation resolves asynchronously throws at runtime.

## Request Bodies

`POST`, `PUT`, `PATCH`, and `QUERY` handlers declare how request bodies are read with the `json` and `form` options. When either is configured, `ctx.body` is a promise for the parsed and validated body. Otherwise `ctx.body` is `undefined`.

```ts
import * as v from "valibot";

export const POST = Run.POST(
  {
    form: v.object({
      name: v.string(),
      age: v.pipe(v.string(), v.toNumber()),
    }),
  },
  async (ctx) => {
    const [data, issues] = await ctx.body;
    if (issues) {
      return Response.json({ issues }, { status: 400 });
    }
    return Response.json(data);
  },
);
```

Like `params` and `search`, `ctx.body` is lazy. The body is only read, parsed, and validated when the promise is consumed, meaning it is awaited or its `.then` method is called. A handler that rejects a request early does so without reading the body at all.

Function validators resolve `ctx.body` to their return value. Standard Schema validators resolve it to a [`[value, issues]` tuple](#standard-schema). With no validator, it resolves to the raw parsed body. Requests exceeding the configured size limits below are rejected.

> [!NOTE]
> `ctx.body` is a promise, not a function. Read the parsed body with `await ctx.body`, there is nothing to call.

Aside from runtime safety and typing convenience, validation makes reading request bodies declarative and avoids manual parsing.

```ts
// ❌ Bypasses validation and the size limits below
export const POST = Run.POST(async (ctx) => {
  const note = await ctx.request.json();
  return new Response(JSON.stringify(createNote(note)), {
    headers: { "content-type": "application/json" },
  });
});
```

```ts
// ✅ Parsed, validated, and typed
import * as v from "valibot";

const NoteSchema = v.object({ text: v.pipe(v.string(), v.nonEmpty()) });

export const POST = Run.POST({ json: NoteSchema }, async (ctx) => {
  const [note, issues] = await ctx.body;
  if (issues) {
    return Response.json({ issues }, { status: 422 });
  }
  return Response.json(createNote(note), { status: 201 });
});
```

### `json`

Handles requests with an `application/json` content type. The option can be a validator, or an options object:

| Option      | Default        | Description                                     |
| ----------- | -------------- | ----------------------------------------------- |
| `validator` |                | Function or Standard Schema for the parsed JSON |
| `maxBytes`  | 1048576 (1MiB) | Maximum request body size in bytes              |

### `form`

Handles requests with an `application/x-www-form-urlencoded` or `multipart/form-data` content type. Repeated field names become arrays. The option can be a validator, or an options object:

| Option         | Default                   | Description                                        |
| -------------- | ------------------------- | -------------------------------------------------- |
| `validator`    |                           | Function or Standard Schema for the parsed form    |
| `maxBytes`     | `maxFiles * maxFileBytes` | Maximum request body size in bytes                 |
| `maxParts`     | 1000                      | Maximum number of parts in a multipart request     |
| `maxFiles`     | 20                        | Maximum number of files in a multipart request     |
| `maxFileBytes` | 1048576 (1MiB)            | Maximum size of each uploaded file in bytes        |
| `onFile`       |                           | `(ctx, file) => any` called for each uploaded file |

## Standard Schema

When a validator is a Standard Schema, the validated result is a `[value, issues]` tuple. On success, `issues` is `undefined` and `value` holds the parsed output. On failure, `value` holds the raw input and `issues` describes each problem.

TypeScript treats the tuple as a discriminated union: checking `issues` is what narrows `value` to the parsed type. Narrowing applies to one read of the tuple at a time, so every place that uses the result needs its own check.

```ts
export const POST = Run.POST({ form: signupSchema }, async (ctx, next) => {
  const [signup, issues] = await ctx.body;
  if (issues) {
    return next({ issues });
  }
  return next({ signup }); // downstream sees the narrowed type
});
```

> [!TIP]
> Check `issues` once in the handler and pass the narrowed data to the page with [`next`](./data-loading.md). The template then receives already-validated values instead of repeating the check.

## Merging Options

All options declared for a route, across its middleware and handler, are merged into a single set. When the same option is declared more than once, the last validator wins and object forms are merged shallowly. A `maxBytes` limit declared in one place combines with a `validator` declared in another.

This makes middleware a natural home for shared validation. An options-only `Run.ALL({ search: ... })` in a [`+middleware` file](./file-based-routing.md#middleware) applies to every route below it.

## Next Steps

- [Data Loading](./data-loading.md)
- [Runtime](./runtime.md#context)
- [TypeScript](./typescript.md#generated-types)