Hatchable
Hatchable lets you host full-stack web applications you build, deploy, and manage via MCP tools.. A marketing site, an internal tool, a family recipe tracker — Hatchable provisions a database, writes your files, deploys the backend functions, and hands back a live URL. Just prompt "build ... on hatchable". Personal projects are free forever; publish to the open web or use a custom domain on a paid plan.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- AI App & Website Builders
- Secondary Subcategories
- None listed
- Brand
- Hatchable
- Access
- Account required
- First tracked
- 2026-07-07
- Tool count
- 31
- Geography
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is visible.
ChatGPT Plugin Discoverability Score
ChatGPT organic discovery is not live yet
Hatchable is tracked in the ChatGPT Plugin registry. Public organic-discovery measurement is not live for ChatGPT yet, so there is no score to publish today.
Get notified when your score goes live
Enter your work email and we’ll notify you when ChatGPT Plugin organic discovery scoring launches.
No spam. Unsubscribe any time.
Competing in ChatGPT AI App & Website Builders
View CategoryHow the Discoverability Score works
Organic discovery scoring for Hatchable on ChatGPT is not live yet. The score will use measured agent conversations when it launches.
Organic discovery scoring is pending. Your Plugin score will appear on this scale when measurement goes live.
FoundDiagnostic
Whether Claude found your Plugin in connector search. It must be Found before it can reach the picker, but the score counts picker appearances—not search results.
PickedMain score
How often your Plugin appeared in the picker, or Claude invoked it directly, across contested conversations. This percentage is the Discoverability Score; the headline number is rounded.
PositionedDiagnostic
What position your Plugin appeared in when it was shown in the picker. This shows prominence, but it does not affect the score.
31 tools agents can invoke
Deploy the project. Runs migrations/*.sql (tracked so each runs once), runs seed.sql on first deploy, copies public/ files to the CDN, and registers api/ files as live endpoints. Increments the project version. Always populate `intent` and `summary` so the user sees a readable changelog in the console: - `intent` is what the USER asked for, in their own words. Quote or lightly paraphrase their last instruction. e.g. "Add a split-the-bill section", "Make the buttons rounder". - `summary` is what YOU did, in plain language they can read. 1–3 sentences. e.g. "Added a SplitBill component with a member counter and per-person breakdown. Updated the main page nav to switch between solo and split modes." These become the commit message AND the History row title in the console. A user will likely scroll their History a week from now to remember what they built — write the summary so a future-you-with-no-context understands what shipped. If there is no clear user prompt (autonomous maintenance), leave `intent` blank but still pass a `summary` describing what changed and why. Call this after writing all your files. To verify your functions work after deploying, use `run_function` — it calls the function directly through your authenticated session and works for all project visibilities. The `url` field is the public URL for end users — personal projects require visitors to sign up before they can view the site.
deploy
Create a new Hatchable project. This generates a URL slug, creates a dedicated PostgreSQL database, and returns the project ID and URLs. Call this FIRST, then keep going — creating a project does NOT make anything live. The returned URL is an empty shell that returns 404 until you (1) write your files with `write_files` and (2) call `deploy`. Do not stop, report success, or hand the user the URL until you have deployed. ## Project structure ``` public/ static files, served at their file path api/ backend functions — each file is one endpoint hello.js → /api/hello users/list.js → /api/users/list users/[id].js → /api/users/:id (req.params.id — one segment) docs/[...path].js → /api/docs/*path (req.params.path — string[], catches multi-segment) pages/ server-rendered HTML at clean URLs — each file renders one page index.js → / about.js → /about blog/[id].js → /blog/:id (req.params.id; res.send(html), SSR'd in the isolate) lib/ shared code pool, not routed — import from any file as `lib/<name>.js` migrations/*.sql SQL files, run in filename order on every deploy seed.sql optional — runs on first deploy / fork, once per project hatchable.toml optional overrides (cron, auth, project name) package.json dependencies (no build scripts yet — build locally, commit public/) ``` ### Routing precedence Most-specific wins. For a request to `/api/users/42`: 1. `api/users/42.js` (static) — beats 2. `api/users/[id].js` (single-param, `params.id = "42"`) — beats 3. `api/users/[...rest].js` (catch-all, `params.rest = ["42"]`) Catch-all params arrive as `string[]`, never slash-joined. Use `req.params.path` as an array: `const [first, ...rest] = req.params.path;` ### Page & static resolution (the clean-URL order) A request to a non-`/api/` URL like `/about` resolves in this order: 1. **Exact static file** — `public/about`, `public/about.html`, `public/about/index.html` 2. **`pages/` handler** — `pages/about.js` (server-rendered in the isolate; see skill `pages/server-render-a-page`) 3. **Ancestor `index.html` fallback** — walks up: `public/foo/index.html` → `public/index.html` (the SPA shell) A committed static file always wins over a page handler at the same URL, and a page handler always wins over the SPA shell — so pick `public/` *or* `pages/` for a given route, not both. Step 3 means each folder with an `index.html` acts as its own mini-site: ship an `/admin/*` SPA beside a static marketing `/` and unmatched paths under `/admin/` fall back to `public/admin/index.html`. Use `pages/` when the HTML must be filled in server-side (share/OG/SEO pages, signed-in first paint); use `public/` + Alpine for everything else. ## Handler contract Every file under api/ exports a default async function: ```js // api/notes/list.js import { db } from "hatchable"; export const access = "member"; // require a signed-in collaborator (edge-gated) export default async function (req, res) { // The edge already gated the route; req.member is the signed-in caller. const member = req.member; // { id, handle, ... } const { rows } = await db.query( "SELECT id, title FROM notes WHERE author_id = $1", [member.id] ); res.json(rows); } // Optional: restrict methods export const methods = ["GET"]; // Optional: register this endpoint as a recurring scheduled task. // Minimum interval is hourly. See also: scheduler.at() in the SDK // for imperative / one-shot / per-firing-payload scheduling. // export const schedule = "0 */6 * * *"; ``` ### req (Express-shaped) - method, url, path, headers, cookies, params, query - body — parsed by Content-Type: JSON → object, urlencoded → object, multipart/form-data → object of non-file fields - files — present for multipart uploads: [{ field, filename, contentType, buffer }] ### res (Express-shaped) - res.json(data), res.status(code) (chainable), res.send(text|buffer) - res.redirect(url), res.cookie(name, value, opts), res.setHeader(name, value) ## SDK — import from "hatchable" Everything you need lives under one import. Do not reach for npm packages that duplicate these — the deploy linter rejects `puppeteer-core`, `@anthropic-ai/sdk`, `pg`, `nodemailer`, `bullmq`, `ioredis`, `@aws-sdk/client-s3`, `child_process`, etc. and points you here. ``` // project storage / SQL db.query(sql, params) → { rows, rowCount } db.transaction([{sql, params}, ...]) → { results: [...] } storage.put(key, buffer, contentType) → url storage.get(key) → { buffer, contentType } storage.del(key) // identity + comms // req.member — signed-in caller, set by the edge; null on anonymous public routes // → { id, handle, email?, display_name?, avatar_url?, role? } | null email.send({ to, subject, html }) // scheduling + background work scheduler.at(when, route, opts?) → declared/armed cron scheduler.cancel(taskId) // browser, AI, knowledge — managed services, no npm install browser.html(url) / browser.pdf(url) / browser.screenshot(url) browser.session(async page => { ... }) → puppeteer-shaped ai.generateText({ model: 'sonnet', prompt | messages, system?, tools?, maxSteps?, purpose? }) ai.streamText(opts) → AsyncIterator ai.embed(input) → { embedding } | { embeddings } knowledge.base(name, { dimensions }).add/search/searchByVector/remove/table ``` External HTTP via global `fetch` (routed through Hatchable's egress proxy automatically). Project secrets are declared in `hatchable.toml` under `[[secret]]`; humans paste values via the platform-rendered setup gate. `ai.generateText` reads keys server-side via the gateway — never via raw `process.env`. ### What you cannot do - Spawn binaries (no `child_process`, no shell). - Persist to local filesystem between requests (use `storage` instead). - Open a long-lived TCP/WebSocket server. - Install npm packages with native bindings — Hatchable does not run `npm install` at deploy. The SDK above replaces every common reason to reach for one. ## Deploy-blocking rules (the four that most often fail a deploy — get them right up front) 1. **Every file under `api/`, `pages/`, and `mcp/` MUST `export const access`** — one of `"public"` | `"member"` | `"admin"` | `"scheduler"`. Omitting it HARD-FAILS the deploy. (`public` = anyone; `member` = signed-in end user via the `[auth]` block; `admin` = project owner; `scheduler` = cron-only.) 2. **No npm packages — including for auth.** The isolate has no `npm install`. For end-user accounts use the **passwordless `[auth]` block** (email-code login; see skill `auth/gate-an-endpoint`) — do NOT hand-roll login with `bcrypt`, `argon2`, `bcryptjs`, or `jsonwebtoken` (all rejected at deploy, and you can't vendor native modules). If you genuinely must hash, use **WebCrypto PBKDF2** (`crypto.subtle`), never bcrypt. Never store or compare plaintext passwords. 3. **SQL runs through a guarded gateway** (`seed.sql`, `migrations/*.sql`, `execute_sql`) — these rules avoid "This SQL operation is not allowed": - One statement per call — no `;`-separated multi-statements. - No `$<digit>` inside string literals (`$29` parses as a bind param) — write `29 USD`. - No standalone `GRANT`/`REVOKE` tokens, even inside string data. - Avoid large multi-row `VALUES` — use one `INSERT ... SELECT ... WHERE NOT EXISTS` per row. - Use `gen_random_uuid()` for UUID defaults. 4. **`/api/auth/*` is reserved** by the platform's auth system — don't define routes there or you'll get a route collision at deploy. ### Scheduling Two ways to schedule a function — pick based on whether the "when" is known at deploy time or at runtime. **Declared** (static, lives in source, reconciled on deploy): ```js // api/nightly-report.js export const schedule = "0 9 * * *"; // 5-field cron, minimum hourly export default async function (req, res) { /* ... */ } ``` **Armed** (dynamic, from user code, preserved across deploys): ```js import { scheduler } from "hatchable"; // recurring — first arg is a 5-field cron string await scheduler.at("0 * * * *", "/api/ping"); // one-shot at a specific moment, with per-firing payload await scheduler.at("2026-05-01T07:00:00Z", "/api/book", { payload: { missionId: 42 } }); // idempotent named arm — repeated calls update the same task await scheduler.at("0 9 * * *", "/api/digest", { name: "daily-digest" }); // cancel by id await scheduler.cancel(taskId); ``` Each firing invokes `route` with `req.headers['x-hatchable-trigger'] === 'cron'` and `req.body === payload`. Use one-shot + payload instead of writing your own "pending jobs" table with a polling cron — that's the pattern the primitive replaces. ## Database Postgres. Write schema in migrations/*.sql. Files run in filename order, tracked in __hatchable_migrations so each runs once. Always use RETURNING to get inserted ids in the same round trip: ```sql INSERT INTO users (email) VALUES ($1) RETURNING id ``` Never call lastval() or LAST_INSERT_ID() — each db.query is a fresh connection, so session-local state doesn't carry across calls. ## Available APIs Functions run in V8 isolates. You get: - The full Hatchable SDK (see above). - Plain JS / TypeScript (no transpile step needed for modern syntax). - `fetch` for external HTTP (routed through Hatchable's egress proxy for quota + accounting; pass through transparently to the URL). - Web Crypto and standard ECMAScript builtins. - Pure-JS npm packages — anything that doesn't need native bindings, filesystem persistence, child processes, or raw sockets. Common ones used regularly: csv-parse, xlsx, bcrypt, jsonwebtoken, uuid, date-fns, lodash, marked, sanitize-html, cheerio, xml2js, qrcode, stripe. - Declared secrets via `process.env.KEY` (only for `[[secret]]` entries in hatchable.toml that have `expose = true`; the project owner pastes the value through the setup gate). Most secrets are SDK-mediated and never reach process.env — see the secrets docs. What's NOT available — and the SDK alternative: | You wanted | Use this | |---|---| | `puppeteer-core` / chromium | `import { browser } from "hatchable"` | | `pg` / `mysql2` / SQL drivers | `import { db } from "hatchable"` | | `@anthropic-ai/sdk` / `openai` | `import { ai } from "hatchable"` (BYOK — set ANTHROPIC_API_KEY in project env) | | `nodemailer` / `@sendgrid/mail` | `import { email } from "hatchable"` | | `@aws-sdk/client-s3` | `import { storage } from "hatchable"` | | `ioredis` / `@upstash/redis` | `db` — use a Postgres table for KV-shaped state (Redis clients aren't available) | | `bullmq` / `bull` | `import { tasks } from "hatchable"` | | `sharp` / `jimp` | URL-based storage transforms (planned); `browser.screenshot` for HTML→image | | `fs.writeFileSync('/tmp/...')` | `storage.put(key, bytes)` | | `child_process.spawn` | not available — use `browser` for chromium, file an issue otherwise | The deploy linter rejects deploys that import the deny-listed packages and points you at the right SDK module by name. You'll see the redirect message before the deploy lands. ## Calling the API from public/ At deploy time, Hatchable injects a tiny bootstrap into every HTML file: ```js window.__HATCHABLE__ = { slug: "my-app", api: "/api" }; ``` Use it as the base URL: ```js const API = window.__HATCHABLE__.api; fetch(API + "/users/list").then(r => r.json()).then(render); ``` ## Identity You never build a login form, session, or users table — the platform owns identity. Every route declares `export const access` (`public` | `member` | `admin` | `scheduler`); the edge authenticates the caller and gates the route BEFORE your handler runs. - To require a signed-in person, declare `access: 'member'`. The edge bounces anonymous visitors to login; inside the handler `req.member` is guaranteed present. - Read who's calling from `req.member` → `{ id, handle, email?, display_name?, avatar_url?, role? }` (null on anonymous `'public'` routes; `email` present for any authenticated caller). Use it to scope rows / stamp `created_by`; you don't check it for authorization, the edge already did. - Gate the owner/operator surface with `access: 'admin'` (or the `admin.*` SDK for in-handler profile/redirect). ```js // api/notes/list.js — only signed-in collaborators reach this import { db } from "hatchable"; export const access = "member"; export default async function (req, res) { const member = req.member; const { rows } = await db.query( "SELECT * FROM notes WHERE author_id = $1", [member.id] ); res.json(rows); } ``` See skills `api/access`, `auth/gate-an-endpoint`, `auth/handle-the-anonymous-case`, and `admin/recognize-the-project-admin`. ## Deploy After writing files, call the `deploy` tool. It runs migrations, seeds (first deploy only), copies public/ to the CDN, and registers api/ routes.
Delete a project file. Takes effect after the next deploy. Optional `reason`: surfaces in the History view so the user understands why the file was removed.
Run every deploy-time validator against the project's current files without actually deploying. Returns `errors` (hard gates) and `warnings` (soft lints), plus a `would_deploy` summary of what would ship. Errors catch: package.json build scripts, reserved table names in migrations, auth route collisions, usage cap breaches. Warnings catch known runtime footguns that type-check but silently misbehave — most notably `db.query()` / `ai.generateText()` / `config.get()` calls without `await` (returning a Promise is truthy, so `if (!result)` guards pass and downstream property reads are undefined). Safer than calling deploy blindly and finding out mid-flight.
Run SQL against the project's dedicated PostgreSQL database. Supports: CREATE TABLE, ALTER TABLE, DROP TABLE, INSERT, SELECT, UPDATE, DELETE. Use parameterized queries for safety: pass values in the `params` array with $1, $2, etc. placeholders. Guarded gateway — these avoid "This SQL operation is not allowed": - One statement per call — no `;`-separated multi-statements. - No `$<digit>` inside string literals ($29 parses as a bind param) — write "29 USD", or pass it via `params`. - No standalone GRANT/REVOKE tokens, even inside string data. - Avoid large multi-row VALUES — use one `INSERT ... SELECT ... WHERE NOT EXISTS` per row. - Use `gen_random_uuid()` for UUID defaults. Return format: - SELECT: { rows: [...], count: N } — DECIMAL columns return as strings (e.g. "45.00") - INSERT/UPDATE/DELETE: { changes: N } - DDL: { changes: 0 }
Fork a public project into your account. Copies all code and database schema (no data). The fork starts as a personal project you can modify freely. This is the recommended way to start from an existing app: fork it, then modify the code.
Detail view of one deployment by version number — returns the full file manifest (paths, hashes, sizes) and function list captured when that version shipped. Use it with list_deployments to audit or compare what changed between versions.
Get project details including slug, status, deployed functions, and the database schema (tables, columns, types).
Return the database schema for the project's PostgreSQL database: tables, columns (with types), and indexes.
Regex content search across a project's files. Postgres-backed, scoped to one project, with glob filtering. Three output modes: - files_with_matches (default) — list paths containing a match - content — matching lines with optional context and line numbers - count — per-file match counts + total Default head_limit is 250 to prevent context blowups on broad patterns. Use glob to narrow by path (e.g. 'api/**/*.js', 'public/**/*.html'). Regex uses Postgres syntax (~ / ~*). Invalid or catastrophic patterns error out via a 2s statement timeout — simplify the pattern if that happens.
Fetch a remote URL and save the response body as a project file — server-side, so the bytes never pass through your context window. Useful for seed data, vendor libs, and asset migration. Capped at 10 MB and 10s timeout. Private/loopback addresses are rejected. Path must live under public/, api/, or migrations/, or be one of seed.sql / hatchable.toml / package.json.
List every scheduled task for a project. A task points at a function and carries a cron expression (recurring) or a one-shot fire_at, plus an optional payload delivered as the request body. Tasks are either 'declared' (written into source via export const schedule or hatchable.toml, reconciled on deploy) or 'armed' (inserted by the SDK scheduler.at() call, preserved across deploys). Response includes next_fire_at, last_fired_at, attempts, last_error, and 7-day run/error counts from FunctionLog. Diagnostic: if next_fire_at keeps moving forward but last_fired_at never advances, the scheduler isn't running.
List deployments for a project in reverse-chronological order. Each entry includes version, status, deployed_at, description, and summary counts (files, functions). Use this to understand recent deploy history, identify a known-good version for rollback, or debug a regression by comparing two versions.
List all files in a project with their paths, sizes, and hashes.
List every deployed API function for a project: route, method, runtime tier, type ('scheduled' if the function has at least one active scheduled task, else 'api'), and 24-hour invocation and error counts. This is the 'what routes did I ship' introspection tool. Call it after a fork, after picking up an unfamiliar project, or to verify a deploy registered the endpoints you expected. Much cheaper than reading every api/ file with read_file. For scheduling details (cron, fire_at, payload, run history) use list_cron_jobs.
Show multipart uploads currently staged for this project that haven't yet been committed. Use this to recover from a disconnect — find the upload_id and resume from the next chunk_index. Uploads expire 10 minutes after the last chunk was added.
List all projects you own or collaborate on, with their tier, role, and current version.
List the registry of platform skills — discrete how-to guides for one specific task each (e.g. 'gate-an-endpoint', 'add-a-cron-job', 'add-rag-search'). Each entry is a name, one-line purpose, and category. Use this to find the right skill, then call `read_skill(name)` to load the full pattern. When in doubt about how a Hatchable feature works, **list_skills first**. The skills are the canonical, agent-tested patterns. They beat guessing or reading the verbose docs. Filter by `query` (matches name + purpose) or `tag` (auth, data, ai, ops, etc.). Without filters, returns the full registry (~35 entries).
Apply a targeted edit to an existing project file without rewriting the entire file. Finds the first occurrence of `old_string` and replaces it with `new_string`. Use this instead of write_file when modifying large files (e.g. HTML) — you only send the changed portion, not the whole file. The old_string must match exactly (including whitespace). If it's not found, the tool returns an error. To insert at a specific position, use a nearby string as old_string and include it in new_string with your addition. Optional `reason`: short note about why this patch — surfaces in the console's History view next to the file's diff. Include when the patch's purpose diverges from the deploy's overall intent.
Read the content of a project file. Pass offset/limit to read a range of lines — useful for large files where the whole file would blow the context window. When either is set, the response includes cat -n style line-numbered content so subsequent patch_file calls can reference exact line numbers.
Load the full markdown body of one skill: when to use it, the canonical code shape, common pitfalls, and how to verify it works. Skills are the platform's primary agent-facing reference — every pattern an agent might need is one of these. Pass either the bare name (e.g. 'gate-an-endpoint') or the category-qualified path (e.g. 'auth/gate-an-endpoint'). Use `list_skills` first to discover names.
Execute arbitrary JS in the project's isolate runtime. The SDK is pre-imported into local scope — `db`, `auth`, `email`, `storage`, `ai`, `agent`, `cache`, `knowledge`, `memory`, `tasks`, `scheduler`, `browser`, `run`, `approval` are ready to use without import. `process.env` and global `fetch` also work. `return` to produce the `result` field. Top-level `import` and dynamic `import('hatchable')` are NOT supported in this REPL — the bindings above are how you reach the SDK. Use this as a REPL: probe the database, verify a computation, test an API shape before committing it to a file. Nothing is persisted — the snippet runs once and disappears. Caps: 5s default timeout (max 30s), 256 KB max source length. Example: run_code({ project_id, code: ` const { rows } = await db.query("SELECT count(*) FROM users"); return rows[0]; `})
Execute a deployed function and return the real response. Use this to test your API endpoints. Returns: { status, headers, body, logs, error, duration_ms } Example: run_function({ project_id: 1, path: "/api/users", method: "GET" }) Example: run_function({ project_id: 1, path: "/api/users", method: "POST", body: { name: "Alice" } }) VERIFY EACH ACCESS TIER with `as`: run the route as it would behave for a 'public' (anonymous), 'member', or 'admin' caller. The route's declared `access` is enforced — so as:'public' on a member-only route returns the real 401, as:'member' on an admin route returns 403, and an allowed tier runs the handler with req.member synthesized for that role. Use this to confirm both 'the page works for a member' AND 'the gate blocks the public'. Without `as`, runs as you (the owner, full access). IMPORTANT: Always run_function on your API endpoints after writing them. Inspect the response body field names and types. Then write your frontend to match those exact names.
Search Hatchable's own documentation for platform behavior — routing, the SDK surface, deploy semantics, auth config, runtime limits. Call this instead of guessing when you're unsure how a Hatchable feature works. Ranks results by term frequency across headed sections. Returns source file, section heading, and a snippet around the hit.
Search the public Hatchable project directory — other people's projects that you can view or fork. Use this to find existing apps to fork-and-modify as a starting point. Note: this searches the public *marketplace*. To search inside your own project's files, use the `grep` tool instead.
Tell the Hatchable team about a platform footgun, friction point, or surprising behavior you hit during this build. Reports go straight into the platform's triage queue and turn into bug fixes, doc updates, or explicit decisions. **When to call:** any time a platform constraint, undocumented limit, misleading error, missing helper, or stale doc cost you more than ~5 minutes to figure out — OR any time you successfully reach for a non-obvious workaround that future builds shouldn't have to rediscover. Calling mid-build (right after the workaround) is more useful than at the end of the build, because the painful details are still fresh. **Report quality matters:** the title should be one sentence ("TextDecoder caps decoded strings at 32 KB per decode() call"). The body should describe what you tried, the error you got, and the workaround. Reports become Github issues / docs PRs verbatim — write for the engineer who'll fix it, not for yourself. **Don't use this for:** generic praise, app-level bugs in the user's own code, anything that's already documented (search skills first via list_skills).
Update project metadata: name, tagline, description, category, is_template. Only the fields you pass are touched. Slug and tier are immutable from MCP. Setting `is_template: true` lists the project in the Templates gallery so other users can fork it.
Multipart file upload for content that exceeds a single model response's output token cap (big SPA bundles, large seed data, inline vendor libs). Flow: first call with chunk_index=0 and NO upload_id — response returns an upload_id. Subsequent calls pass that upload_id with chunk_index=1, 2, 3…. Last call sets final=true to atomically concatenate and commit as one ProjectFile. Chunks are staged in Redis with a 10-minute TTL. chunk_index overwrites (safe to retry). Max chunk size: 64 KB. Max assembled file: 20 MB.
View function execution logs with rich filtering. Each entry includes status_code, duration_ms, log_output (captured console.log), error (if any), and a derived `level` field (error/warning/info). Filter by any combination of function_name, route, method, status_code (exact or 4xx/5xx wildcards), level, time range (since/until — ISO or relative like '1h'/'30m'/'7d'), full-text query across log_output and error, or specific request_id. Use this to debug production issues: e.g. `level='error'` + `since='1h'` finds everything that blew up in the last hour.
Write or overwrite a project file. Paths are relative to the project root. Valid locations: public/** static files (HTML, CSS, JS, images, etc.) api/**.js backend functions (each file is one endpoint) pages/**.js server-rendered HTML at clean URLs (pages/about.js → /about) lib/** shared code pool, not routed — import anywhere as `lib/<name>.js` migrations/*.sql database migrations, run in filename order seed.sql optional seed data, runs once on fresh installs hatchable.toml optional config overrides package.json dependencies (no build script yet) Files are stored but not live until you call `deploy`. Editing an existing file? Don't re-send the whole thing — call `read_file` to fetch current contents, then `patch_file` to change just the lines you need (or `write_files` to update several files at once). `write_file` overwrites the entire file, so reserve it for new files or full rewrites. Optional `reason`: a short note about why THIS specific file edit is happening. Skip it when the reason is obvious from the deploy's overall intent (most edits). Include it when the per-file purpose meaningfully diverges — e.g. "bumped vue 3.4 → 3.5 to fix the reactivity bug" on a package.json edit during an unrelated feature deploy. The reason shows up in the console next to the file's diff in the History view.
Write multiple project files in a single call. Same rules as write_file but batched — faster for scaffolding a new project or updating several files at once. Each entry in the files array has a path and content. All files are written atomically — if any path is invalid, none are written. Optional top-level `reason` applies to the whole batch (typical: one logical change touching many files). Per-entry `reason` overrides the batch reason for that specific file when their purposes diverge.
How do I improve a ChatGPT Plugin's discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Hatchable alternatives on ChatGPT?
As of 2026-08-14, Hatchable competes with Adalo, AnswerBack, AppDeploy, Base44, Buildfire, Floot, Hercules, Hostinger, Lovable, MiniUp, Replit, Sticklight, Val Town, Zite in ChatGPT AI App & Website Builders, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.