Developer docs  /  Project structure
The framework

A project is a folder.

Hatchable is an agent-first framework for full-stack web apps. The entire contract is the file tree: paths become routes, SQL files become schema, one config file declares everything else. No build step, no boilerplate, nothing hidden.

The shape of a project

Every directory has one job. The path of a file decides what the platform does with it:

That is the whole framework. There is no router file, no controller registry, no dependency injection to configure. If a file exists at the right path with the right shape, it is live after the next deploy.

A complete app in four files

This is a working todo app. Not a fragment: the entire thing.

migrations/0001_create_todos.sql
CREATE TABLE todos (
  id BIGSERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  done BOOLEAN NOT NULL DEFAULT FALSE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
api/todos.js
import { db } from 'hatchable';

export const access = 'public';
export const methods = ['GET', 'POST'];

export default async function (req, res) {
  if (req.method === 'POST') {
    const { rows } = await db.query(
      'INSERT INTO todos (title) VALUES ($1) RETURNING *',
      [req.body.title]
    );
    return res.json(rows[0]);
  }
  const { rows } = await db.query('SELECT * FROM todos ORDER BY id DESC');
  res.json(rows);
}
public/index.html
<!doctype html>
<html><body>
  <h1>Todos</h1>
  <form id="f"><input name="title" required><button>Add</button></form>
  <ul id="list"></ul>
  <script>
    const load = async () => {
      const todos = await (await fetch('/api/todos')).json();
      list.innerHTML = todos.map(t => `<li>${t.title}</li>`).join('');
    };
    f.onsubmit = async (e) => {
      e.preventDefault();
      await fetch('/api/todos', { method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ title: f.title.value }) });
      f.reset(); load();
    };
    load();
  </script>
</body></html>
hatchable.toml   (optional for this app; here for completeness)
# nothing required yet. auth, cron, secrets and buyer-editable
# config are declared here when you need them.

Zip the folder, or push it to a public GitHub repository, and import it. The platform creates the database, applies the migration, registers the route, and serves the page. You get a URL, hosting, TLS, and a per-project PostgreSQL database without writing a line of infrastructure.

Where is the users table? You never build login. Add [auth] enabled = true to hatchable.toml and the platform owns identity end to end; your handlers just read who is calling. See identity in the SDK reference.

The file contract

The reference below is served to every connected agent as part of the project manifest, and rendered here from the same source. If it says something, both you and your agent are reading the identical rule.

File conventions

File-based routing. Path determines the URL.

Path pattern What it becomes
api/users.js /api/users
api/users/[id].js /api/users/:id (id available as req.params.id)
api/docs/[...path].js /api/docs/*path (catch-all; req.params.path is string[])
api/index.js /api (root)
api/users/index.js /api/users
migrations/*.sql Applied at deploy in lexical order. Forward-only.
public/* Static assets served at the same path
hatchable.toml Project-level config: [auth], [ai], [[cron]], [[secret]], [[api]], [[config]], [mcp]. (Custom domains are managed in the console, not here.)
package.json Validated at deploy. Framework/ORM deps and scripts.build are rejected (see the deploy validators). Hatchable does not run npm install.

Cron scheduling: add export const schedule = "0 * * * *" to a handler, OR add a [[cron]] block in hatchable.toml with path = "/api/..." and schedule = "...". The key is path (NOT route — a route = entry is silently ignored and never fires). One schedule per route. Minimum granularity is hourly for every project.

Naming for migrations: NNNN_description.sql where NNNN is a zero-padded sequence number. Migrations run in lexical order, once per database.

Why agent-first changes the anatomy

Three parts of the tree exist because agents are first-class users of the framework, not an add-on:

  • mcp/*.js makes your app itself an agent surface. Each file declares one tool, and the platform serves them from your app's own MCP endpoint. Your users' agents can operate the app you built, with per-tool access levels. See project MCP tools.
  • Every project publishes a live manifest. An agent connected as the owner or an admin collaborator gets the project's current tables, routes, schedules and the names of its configured env variables, stitched fresh on every read. Values are never included: keys stay server-side and resolve through the SDK. A new chat session starts already knowing the shape of the project without being handed a single secret.
  • The conventions are machine-checkable. Because routing, access and config are declarative, the deploy validator can verify the whole contract before anything ships, and tell an agent exactly what to fix. Humans get the same errors in the console's guided flow.

None of this stops you from hand-writing every file. The framework reads the same either way; that is the point.