# URLCode **A portable runtime for programmable URL behavior, and the framework that grows from it.** Declare an application's public URL surface in YAML, add JavaScript only where declarative handlers are not enough, and run the same project locally, in a container, on your own infrastructure or on a provider adapter. When the project gets serious, add accounts, data and tools as operator-installed extensions instead of building them again. **URL behavior as code.** [![Verify](https://github.com/jimhoyd-com/urlcode/actions/workflows/ci.yml/badge.svg)](https://github.com/jimhoyd-com/urlcode/actions/workflows/ci.yml) [Documentation](docs/README.md) · [Concepts][docs/CONCEPTS.md] · [The framework][docs/FRAMEWORK.md] · [For AI agents](llms.txt) · [URLCode AI](https://urlcode.ai/) · [Starter][docs/STARTERS.md] · [Contributing][CONTRIBUTING.md] · [Security](SECURITY.md) **Your AI should build your application, not your framework.** Coding agents rebuild the same routing, validation, middleware, policies and authentication plumbing on every project, and the person ends up owning the plumbing. URLCode represents those behaviors as a strict, portable YAML contract that both people and agents can read: the agent describes what, the runtime owns how, and generated code goes to the part that is actually the application. It is infrastructure for AI-built software, not a framework for building AI models. [Why][docs/PROJECT-DIRECTION.md#why-your-ai-should-build-your-application-not-your-framework] · [roadmap][ROADMAP.md]. ## Install URLCode needs Node.js 22.13 or later. Pick the installation channel that fits your environment: ```sh # npm npm install --global @jimhoyd/urlcode@latest # Homebrew brew tap jimhoyd-com/urlcode brew trust jimhoyd-com/urlcode brew install urlcode # Generic installer (downloads a release tarball and verifies its SHA-256) curl -fsSL https://raw.githubusercontent.com/jimhoyd-com/urlcode/main/install.sh | sh ``` For project-local installs, exact version pins, and provenance details, see [the installation guide][docs/INSTALL.md]. ## Try it ```sh urlcode init my-urls && cd my-urls urlcode dev ``` Without a global install, pin the runtime in the project and run the installed copy (`--no` refuses to fetch anything from the registry): ```sh mkdir my-urls && cd my-urls && npm init -y npm install --save-dev --save-exact @jimhoyd/urlcode npx --no --package @jimhoyd/urlcode urlcode init . npm run dev ``` The starter deliberately has no routes. Ask the local MCP `get_context` tool (or run `urlcode context` in the site; the route project is `app/`), then add the smallest declarative route or custom code the application needs. `urlcode test` runs the project's HTTP fixtures, and `urlcode studio` opens a read-only page on localhost showing what each route does and what needs attention ([review report and studio](docs/TOOLING.md#review-report)). The [install guide][docs/INSTALL.md] covers the checksum-verified script, project-local installs, the container image and signed provenance. To work from a clone: `git clone … && make dev`. ## A URL that runs your function ```yaml version: "1" routes: /hello/{name}: parameters: - { name: name, in: path, required: true, schema: { type: string, minLength: 1, maxLength: 80 } } function: source: functions/hello.mjs args: { name: { from: path, name: name } } env: GREETING: { value: Hello } ``` ```js export default function hello(request, { args, env }) { return Response.json({ message: `${env.GREETING}, ${args.name}!` }); } ``` Add `middleware: [{ source: middleware/headers.mjs }]` to wrap any handler with `await next()`. Fourteen ready-made middleware patterns ship in the cookbook and as `urlcode recipes add middleware`. See [functions and the sandbox](docs/FUNCTION-SECURITY.md) and [middleware][docs/MIDDLEWARE.md]. ## Documentation New to the vocabulary? Read [Concepts][docs/CONCEPTS.md] first — route, handler, middleware, policy, extension; project vs operator; trusted vs sandbox; extension vs artifact. Start with the [YAML guide and recipe book][docs/YAML-GUIDE.md], [complete field reference](docs/YAML-REFERENCE.md), and [runnable 40-route cookbook](examples/cookbook/README.md). For AI-assisted authoring, use [the AI guide](docs/AI-AUTHORING.md), the bundled agent skills ([authoring](.claude/skills/urlcode-authoring/SKILL.md), [operations](.claude/skills/urlcode-operations/SKILL.md)) and [llms.txt](llms.txt). For optional hosted reference and shared skills, use [URLCode AI](https://urlcode.ai/); its anonymous remote MCP augments, rather than replaces, the local project server. Agents can also read installed artifacts (inert extension schemas and examples) through the read-only MCP tools described in [artifacts][docs/EXTENSIONS.md#artifacts]. Follow [organization and readability practices][docs/BEST-PRACTICES.md] as your project grows. Operators should read [capacity/concurrency][docs/CAPACITY.md] and the [DDoS and recovery playbook][docs/RESILIENCE.md]. Embedding the runtime from TypeScript is covered in [TypeScript][docs/TYPESCRIPT.md]. [All documentation](docs/README.md). All of it lives in [`docs/`](docs/README.md) in this repository. See [the roadmap][ROADMAP.md#history] for the retired `urlcode-docs` site repository. ## The framework Core plus three optional bundled extensions and one artifact, one site shape. A project climbs from redirects to a full application by adding YAML; the operator wires trusted extensions in one host file outside the project. The full map, the composition contract and the rules an AI agent must follow are in [the framework][docs/FRAMEWORK.md]. | Component | Adds | Distribution | |---|---|---| | [urlcode](https://github.com/jimhoyd-com/urlcode) (this repository) | Runtime, CLI, policies, provider adapters, extension contract | [npm](https://www.npmjs.com/package/@jimhoyd/urlcode), [GitHub Releases](https://github.com/jimhoyd-com/urlcode/releases), Homebrew | | [auth][packages/auth] extension | Accounts and sessions from [Better Auth](https://better-auth.com/) on one mount; `auth: true` routes receive the signed-in user id | add-on on core's GitHub Release | | [store][packages/store] extension | Durable bounded JSON collections exposed as a typed CRUD API, with an optional audit log kept in the same database and a tap to forward it | add-on on core's GitHub Release | | [mcp][packages/mcp] extension | Declarative MCP tool server over a project-declared tool map | add-on on core's GitHub Release | | [store-schema][artifacts/store-schema] artifact | Inert store configuration schema and example, for tooling | add-on on core's GitHub Release | Only core is published to npm. Every add-on is released as a tarball on the same GitHub Release, at core's version, and core pins each by URL and sha512. A site adds them with `urlcode extensions add ` or `urlcode artifacts add ` ([add-ons][docs/EXTENSIONS.md#add-ons-extensions-and-artifacts]); `urlcode upgrade` moves core and every add-on together. See [package and channel alignment](docs/VERSION-ALIGNMENT.md). `urlcode-dynamic-link` and `urlcode-short` were published once as `0.1.0-alpha.1` and have since been retired: both were unpublished from npm and their repositories deleted, and neither has a direct successor. A project that wants stored short links declares a collection through the `store` extension (see [docs/STORE.md][docs/STORE.md]) instead. Anything still pinned to `@jimhoyd/urlcode-dynamic-link@0.1.0-alpha.1` also has to deal with its exact declared peer `@jimhoyd/urlcode: 0.4.0-alpha.1`, which cannot be installed beside core `0.4.0-alpha.2` and never will be. `urlcode-middleware` was retired too: **per-route middleware is native to core**, through the `middleware:` array documented in [docs/MIDDLEWARE.md][docs/MIDDLEWARE.md]. See [the roadmap][ROADMAP.md#history] for that retirement's history and migration note. ```yaml version: "1" extensions: auth: { version: "1", config: {} } routes: /go: { redirect: { url: https://example.com, status: 302 } } /api/auth/*: { extension: auth, methods: [GET, POST] } /private: respond: { text: Signed in } auth: true ``` The YAML names logical extensions; it never names packages, code, databases or credentials. `urlcode init site --with auth` writes the route project in `site/app/` with auth's `/api/auth/*` mount, the operator host `site/host.mjs` and a private `data/auth.secret`; `npx urlcode-auth migrate` creates Better Auth's tables, and `urlcode serve --host-file host.mjs` loads it. Browsers sign in through Better Auth's own client. Cross-repository acceptance is tracked in [issue 58](https://github.com/jimhoyd-com/urlcode/issues/58). ## What it is A project is a `urlcode.yaml` with `version: "1"`. Each route has exactly one handler: `redirect`, `respond`, `page`, `static`, `download`, `function`, `proxy`, `conditional` or an `extension` mount, with optional ordered `middleware`. The runtime validates the whole project before serving it, compiles it once, and refuses anything a target cannot enforce with the route named. Functions and middleware run trusted, in-process, with full Node access by default; a route opts into an isolated QuickJS/WebAssembly sandbox with a fresh heap per call and no Node, filesystem or network by declaring `sandbox: true`. Either way, the `env`/`secrets` the runtime *hands* a route come only from operator grants pinned to the project revision; grants govern that injected context, not the ambient Node environment a trusted, in-process module can reach on its own like any other code in the host. URLCode is not a URL shortener: stored short links are declared through the operator-installed `store` extension's collections, not core's job. It is not a general Node web framework: routing, validation, middleware wiring and policies are declared in YAML, not hand-wired; isolating a specific route's code from the host is an explicit `sandbox: true` opt-in, not something every route gets by writing a handler. It is not a provider configuration format: infrastructure settings stay out of route YAML. See [project direction][docs/PROJECT-DIRECTION.md], or [Concepts][docs/CONCEPTS.md] for the vocabulary these two paragraphs use. ## Status This checkout prepares the `0.6.6` core release. The auth, store and mcp extensions and the store-schema artifact are workspace packages released as add-on tarballs with core, not npm packages. Package availability remains a live registry fact: see the GitHub Releases page or `npm view @jimhoyd/urlcode dist-tags`. A stable core version does not close the review and deployment evidence gaps below. `0.4.0-alpha.1` added the extension contract, capabilities and provider conformance, strict redirect interchange (since removed), bulk import, recipes and search, TypeScript guest authoring, conditions, bounded proxy and signals, and the MCP read and authoring modes. `0.4.0-alpha.2` then made `function` and `middleware` routes run trusted and unsandboxed by default, with `sandbox: true` as a per-route opt-in, and removed the native `link:`/`dynamicLinks:` YAML shape. That is a behaviour change for existing projects with no YAML edit; read [the roadmap entry][ROADMAP.md] before upgrading. Use the schema and docs from the runtime revision you run. The [roadmap][ROADMAP.md] separates implemented from planned, and [production readiness][docs/RELEASE-OPERATIONS.md#production-readiness] records what is proven and what is not: provider deployments, soak and independent security review remain open. URLCode is free and open-source software licensed under the [Apache License 2.0](LICENSE). Commercial use, modification, redistribution and self-hosting are permitted under its terms. ## Start your own project `urlcode init` creates a bare, agent-ready site with no routes yet. The runtime is a pinned dependency of the site; no separate checkout or global installation is needed. ```sh npx @jimhoyd/urlcode init my-links cd my-links npm install npm run dev ``` ## Start from YAML Already wrote `urlcode.yaml`? Run `urlcode scaffold --project ./my-links --dry-run`, then remove `--dry-run` to create missing modules, pages and directories. Existing files are preserved; code placeholders return 501 until implemented. [Scaffolding guide][docs/SCAFFOLDING.md]. Node 22.13+ installed, 22.18+ to run the TypeScript source. The `auth` extension runs on the Node target only ([auth][packages/auth/README.md]). ## Everything else in YAML - **Pages, files, downloads:** `page`, `static`, `download` with MIME detection, ETags, ranges and safety limits. [Assets][docs/ASSETS.md]. - **HTTP:** methods, validated path/query/header inputs, body limits, response headers and cookies. [HTTP][docs/HTTP.md]. - **Policies and site conventions:** throttle, agents, security headers, compression, cache; robots, sitemap, favicon, security.txt, llms.txt. [Policies][docs/POLICIES.md], [site][docs/SITE.md]. - **Conditions, proxy, signals:** exact predicates with disjoint cases; a bounded HTTPS proxy and best-effort webhooks behind operator grants. [Conditions][docs/CONDITIONS.md], [egress][docs/EGRESS.md]. - **Organization:** `includes` across folders; strict CSV/JSON/YAML redirect import; searchable recipes and examples. [Organization][docs/ORGANIZATION.md], [bulk][docs/BULK.md], [recipes][docs/RECIPES.md]. - **Checks:** `validate`, `test`, `routes`, `audit --expect-routes`, `capabilities`, deployment verification and a GitHub Action. [Readiness][docs/READINESS.md], [CI][docs/CI.md]. ## Deploy Self-hosted Node process or container first. `@jimhoyd/urlcode/vercel` and `@jimhoyd/urlcode/aws` serve declarative projects as native handlers; `urlcode build --target cloudflare` compiles redirects and declared responses into a Worker; `urlcode build --target static` compiles redirects and static files into plain objects and redirect metadata for S3 + CloudFront, with no server at all. Each target refuses at activation or build time what it cannot run, with the route named. None has been exercised on its provider yet; the adapters have local conformance tests only. [Operations][docs/OPERATIONS.md], [capabilities][docs/CAPABILITIES.md], [Vercel][docs/VERCEL.md], [AWS][docs/AWS.md], [Cloudflare][docs/CLOUDFLARE.md], [static hosting][docs/STATIC.md]. ## For AI agents [llms.txt](llms.txt) is the compact index; [the framework][docs/FRAMEWORK.md] is the map; [AI authoring](docs/AI-AUTHORING.md) is the contract with the capability matrix and a copyable task prompt. `urlcode mcp` exposes read-only inspection, validation and conversion previews over stdio, and `--allow-authoring` adds project-confined authoring tools and runner tools that execute the project's code ([tooling](docs/TOOLING.md)). [URLCode AI](https://urlcode.ai/) is the optional hosted companion for version-pinned reference and shared skills; its anonymous remote MCP endpoint is documented in [tooling](docs/TOOLING.md#optional-hosted-ai-mcp). ## Built with URLCode No public application built on URLCode is currently listed. The reference application is the [private-requests proof][proofs/private-requests/README.md] in this repository: owner-private records, a reviewed approval and a reviewer permission declared over the auth and store extensions, with no application server code. Its [client][proofs/private-requests/client/main.js] is the frontend pattern: the application's own code calling the JSON mounts with `fetch`. The two earlier ones were built on the public runtime as ordinary consumers and have since been retired: `urlcode-docs`, a static documentation site rendered through its own middleware at build time and served through native page/static/download routes, and `urlcode-short`, an account-free short-link demo combining expiring links, QR downloads and a shadcn/ui frontend — URLCode supplied the pages, assets and routing, the application supplied anonymous creation, link storage and its own limits. `urlcode-short`'s repository is deleted, so its build retrospective is no longer reachable; what it recorded about the gap between the runtime and a real application is carried in [principles and open decisions][docs/OPEN-DECISIONS.md] and [roadmap][ROADMAP.md]. ## License and contributing Apache-2.0. Commercial use, modification, redistribution and self-hosting are permitted. See [contributing][CONTRIBUTING.md], [security](SECURITY.md), [governance][GOVERNANCE.md] and the [roadmap][ROADMAP.md]. [docs/CONCEPTS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/CONCEPTS.md [docs/FRAMEWORK.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/FRAMEWORK.md [docs/STARTERS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/STARTERS.md [CONTRIBUTING.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/CONTRIBUTING.md [docs/PROJECT-DIRECTION.md#why-your-ai-should-build-your-application-not-your-framework]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/PROJECT-DIRECTION.md#why-your-ai-should-build-your-application-not-your-framework [ROADMAP.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/ROADMAP.md [docs/INSTALL.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/INSTALL.md [docs/MIDDLEWARE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/MIDDLEWARE.md [docs/YAML-GUIDE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/YAML-GUIDE.md [docs/EXTENSIONS.md#artifacts]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/EXTENSIONS.md#artifacts [docs/BEST-PRACTICES.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/BEST-PRACTICES.md [docs/CAPACITY.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/CAPACITY.md [docs/RESILIENCE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/RESILIENCE.md [docs/TYPESCRIPT.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/TYPESCRIPT.md [ROADMAP.md#history]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/ROADMAP.md#history [packages/auth]: https://github.com/jimhoyd-com/urlcode/tree/v0.6.6/packages/auth [packages/store]: https://github.com/jimhoyd-com/urlcode/tree/v0.6.6/packages/store [packages/mcp]: https://github.com/jimhoyd-com/urlcode/tree/v0.6.6/packages/mcp [artifacts/store-schema]: https://github.com/jimhoyd-com/urlcode/tree/v0.6.6/artifacts/store-schema [docs/EXTENSIONS.md#add-ons-extensions-and-artifacts]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/EXTENSIONS.md#add-ons-extensions-and-artifacts [docs/STORE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/STORE.md [docs/PROJECT-DIRECTION.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/PROJECT-DIRECTION.md [docs/RELEASE-OPERATIONS.md#production-readiness]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/RELEASE-OPERATIONS.md#production-readiness [docs/SCAFFOLDING.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/SCAFFOLDING.md [packages/auth/README.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/packages/auth/README.md [docs/ASSETS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/ASSETS.md [docs/HTTP.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/HTTP.md [docs/POLICIES.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/POLICIES.md [docs/SITE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/SITE.md [docs/CONDITIONS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/CONDITIONS.md [docs/EGRESS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/EGRESS.md [docs/ORGANIZATION.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/ORGANIZATION.md [docs/BULK.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/BULK.md [docs/RECIPES.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/RECIPES.md [docs/READINESS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/READINESS.md [docs/CI.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/CI.md [docs/OPERATIONS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/OPERATIONS.md [docs/CAPABILITIES.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/CAPABILITIES.md [docs/VERCEL.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/VERCEL.md [docs/AWS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/AWS.md [docs/CLOUDFLARE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/CLOUDFLARE.md [docs/STATIC.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/STATIC.md [proofs/private-requests/README.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/proofs/private-requests/README.md [proofs/private-requests/client/main.js]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/proofs/private-requests/client/main.js [docs/OPEN-DECISIONS.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/docs/OPEN-DECISIONS.md [GOVERNANCE.md]: https://github.com/jimhoyd-com/urlcode/blob/v0.6.6/GOVERNANCE.md