If this URL cannot be fetched, read the same start text with `npx -y @somewhere-tech/cli@latest docs start` or the MCP `docs({ topic: 'getting-started' })` tool. # Build and deploy your first app on somewhere.tech ## Getting started — build, deploy, verify `somewhere init --name ` in an empty folder writes a local React + TypeScript starter without login. The default includes working sign-in; extend it using the README file map. Minimal/bare starters omit auth. `init --catalog --json` lists modules for `--features`. No account yet? `npx @somewhere-tech/cli deploy` publishes a temporary app and prints its live URL, claim URL, and expiry. Login is needed for account-owned operations, the email test inbox and cron. On a hosted VM, after consent, `somewhere login` prints a code a human approves in their browser; the machine stays signed in. Ask `somewhere advisor ""` how to build or fix it. For a reference, use `somewhere docs --section ` (MCP: `advisor({ question })`, `docs({ topic })`, `catalog`). After every change: 1. `somewhere typecheck`, then `somewhere deploy` (do not build first). Deploy runs the platform compile, schema and secret checks and refuses a blank page before going live; `somewhere deploy-check` runs them without publishing (diagnosis, review, `--run /api/x`). 2. `somewhere verify` — desktop and phone screenshots, console and network health; `somewhere verify --flow flow.json` fills and clicks. Several users: `actors` + `journey` (`somewhere docs browser`). 3. `somewhere browser` inspects a page; `somewhere logs --tail 10`, then `somewhere errors`: read the failure first. 4. Email: sign up as `@.test.somewhere.site`, then `somewhere email test-inbox ` prints the message and its magic link. Routes: `index.html` loads `src/main.tsx`; every extensionless path with no file (`/signin`) serves `index.html`, so one app routes by `location.pathname`. `api/notes/[id].ts` is `/api/notes/:id`; `_`-prefixed names are import-only helpers. Avoid names differing only in letter case (`SignIn.tsx`, `signin.tsx`). With auth enabled, `api/auth/[...path].ts` is `export { somewhereAuth as default } from '@somewhere-tech/sdk/server'`; pages use `createSomewhereAuth()` from `@somewhere-tech/sdk/auth` (no SDK: `docs({ topic: 'auth-client' })`). `auth.signUp({ email, password, displayName? })` / `auth.signIn({ email, password })` return the user or throw with the message. Gate pages on `auth.getState().status` (React: `useAuthState()`) as the auth starter's `src/App.tsx` does. Sign-up signs the user in with `email_verified: false`; unverified users can sign in and pass `auth: 'required'`. `sw.auth.requireUser(req)` returns `{ id, email, role, email_verified, … }` or throws 401 `AUTH_REQUIRED`; `sw.auth.fromRequest(req)` returns it or `null`. Functions: a bare `export default async function (req, sw)` returning a `Response` is always valid. The optional wrapper `sw.endpoint({ auth: 'none' | 'optional' | 'required', body, rateLimit, handler: async ({ body, user, params }, sw) => value })` answers 401/400 itself and sends `value` as JSON. Params: `params.id` there, `sw.params.id` bare. `somewhere typecheck` types bare handlers as `(req: Request, sw: SomewhereRuntimeContext)`. Data: tables in `db/schema.ts` — `owner()` (each user's own rows; no auth guard), `shared()` or `serverOnly()` — with a `client` block for browser access via `somewhere:data`; a custom endpoint enforces its own caller policy. `sw.db.from` / `insert` / `update` / `remove` return `{ data: rows[], count, changes }`; `where: { a: 1, b: { in: ids }, c: { gte: 2 }, d: null }` (one operator per column). Raw `sw.db.query(sql, params)` runs as written (add `WHERE user_id = ?`; managed projects refuse it). Independent reads: one `Promise.all`. Working sequence: https://somewhere.tech/build.txt Before acting, confirm the user asked you to build or deploy; work only in the intended directory and never include secrets or personal data. **When you have real users or data:** `somewhere preview` gives a rotating private URL and an isolated database; promote the exact preview you reviewed. It is not a first-run step. ## The platform shape somewhere.tech runs static files plus serverless functions, not a Node server. Do not write Express/Fastify/Koa, call app.listen(), or bind a port. Do not run `vite build` or `next build`, or deploy dist/ or build/: pre-bundled output deploys, with a warning, but loses source round-trips (export, find/replace patches, the visual editor); `--prebuilt` accepts that. Temporary deploys add a small badge to served HTML (time left, how to keep it); your source is unchanged. npm dependencies in package.json resolve on deploy; native modules are unsupported (`somewhere docs functions`). ## A complete working app (copy this verbatim) Four files, no build. Install once with `npm i -g @somewhere-tech/cli`; the one-off form is `npx @somewhere-tech/cli `. Run these commands from the directory containing the files below. db/schema.ts — public by design: each opt-out is declared on its own line, so the guestbook needs no endpoint: import { id, owner, schema, table, text } from 'somewhere/db'; export default schema({ entries: table({ id: id(), name: text() }, { scope: owner({ visitors: true }), // opt-out: signed-out visitors own rows client: { read: ['id', 'name'], publicRead: true, // opt-out: anyone can read id and name create: ['name'], // with visitors, anyone can add an entry }, }), }); index.html: Guestbook

Guestbook

    src/main.ts — `somewhere:data` is generated from your declaration, so only the operations you granted exist on it: import { data } from 'somewhere:data'; const list = document.getElementById('entries') as HTMLUListElement; const form = document.getElementById('f') as HTMLFormElement; const who = document.getElementById('who') as HTMLInputElement; async function load() { const page = await data.entries.list({ limit: 50 }); list.replaceChildren(...page.data.map((entry) => Object.assign(document.createElement('li'), { textContent: entry.name }))); } form.onsubmit = async (event) => { event.preventDefault(); await data.entries.create({ name: who.value }); form.reset(); await load(); }; load(); tsconfig.json (`somewhere:data` types come from the schema): { "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "Bundler", "strict": true, "noEmit": true, "skipLibCheck": true }, "include": ["src", "db"] } Run `somewhere typecheck`, then `somewhere deploy`. The CLI prints the live URL and links this directory to the temporary project. Save this flow.json beside the other files: { "actions": [ { "fill": "#who", "value": "Benchmark Ada" }, { "click": "#f button" }, { "expect": { "selector": "#entries li", "text": "Benchmark Ada" } } ], "viewports": ["desktop", "mobile"] } Run `somewhere verify --flow flow.json` from the same directory. It submits the form and checks that a fresh database read renders the new entry at both screen sizes. The CLI also checks page, console and network health. `somewhere deploy` creates the table and serves the page; nobody signs in, because of the two opt-outs. Lists come in primary-key order (`page.next`). The page module must be .ts, .tsx or .jsx. A `.js` module is served exactly as you wrote it, so the `somewhere:data` import never resolves. Server functions are the escape hatch: a file under api/ holds a rule the grant cannot express, such as an admin view. ## Declare your tables in db/schema.ts `db/schema.ts` is versioned with the code, so the next session reads the data model instead of guessing. It is a declaration; it never runs. Column types: id, text, number, integer, boolean, timestamp, json, blob. Every table declares a `scope`: `owner()` (per-user rows the platform filters to the signed-in user, never your WHERE clause — `{ visitors: true }` extends that to an unsigned browser), `shared()` (cross-user rows authored by the signed-in user) or `serverOnly()` (functions only). A table is invisible to the browser until it declares a `client` block, and that block is the whole grant: `read` / `create` / `update` name the columns, `delete` is a boolean, `publicRead` opens the `read` columns to visitors who are not signed in, and `identity` is `'authenticated'` or `'visitor'` (derived from the scope). It is enforced on the generated client and on direct HTTP alike; an ungranted operation is refused with DATA_ACCESS_DENIED. Full rules: `somewhere docs declared-data`. Each deploy compares the file with the live database and creates what is missing. `dev` does not apply schema changes; deploy first for this example. To change the schema, edit the file and deploy again; there is no migration step. Additions apply on their own; removing or renaming a table or column is REFUSED on a live database (SCHEMA_DEPLOY_REFUSED); an unchanged file is a no-op. Read the live schema back with the `db_describe` MCP tool or `npx @somewhere-tech/cli db tables`. ## Database rows and app responses Function-side `sw.db.insert`, `update`, and `from` return a result whose `.data` is an array of rows. Each handler chooses its JSON shape: ```ts // Create handler const made = await sw.db.insert('books', { title }); return Response.json({ book: made.data[0] }, { status: 201 }); // Update handler const edited = await sw.db.update('books', { where: { id }, set: { title }, }); if (edited.data.length === 0) return Response.json({ error: 'not_found' }, { status: 404 }); return Response.json({ book: edited.data[0] }); // List handler const listed = await sw.db.from('books', { limit: 50 }); return Response.json({ books: listed.data }); ``` A zero-match update succeeds with `data: []`; the 404 above is your endpoint decision. On an `owner()` table, structured operations apply row ownership from the request; the browser never supplies `user_id`. Cookie helpers (`sw.auth.*WithCookie`) establish browser sessions; bearer login/signup return tokens instead. Add `sw.auth.requireUser(req)` when the endpoint itself requires signed-in callers or has a custom rule. ## Temporary vs Free Anonymous projects expire after 3 hours; claim yours before expiry. Claiming switches to the account plan. Free totals 500 MB database + 5 GB files account-wide; capacity can change. A human opens the claim link and signs in to keep the project; there is no headless claim. ## Secrets, email, payments, AI — after you claim A temporary project has hosting + database + files only. Claiming enables account-owned services such as secrets, email and custom domains, subject to their plan and activation rules. Customer cron requires Builder or higher; claiming onto Free does not enable it. With nowhere to put an API key yet, build the core on sw.db / sw.fs, ship it, then claim to add the rest. ## On a hosted VM, sign in once and stay signed in Ask before running `npx @somewhere-tech/cli login`, and show the printed code exactly for the user to approve in their browser. `somewhere docs setup` has expiry, renewal and revocation. MCP instead of a shell: `https://mcp.somewhere.tech/mcp` (same primitives). Design: https://somewhere.tech/design.txt Link index: https://somewhere.tech/llms.txt Reference: https://somewhere.tech/docs.txt Agent contract: https://somewhere.tech/agent.txt