# Quickstart

Scaffold, run, and deploy a real-time app.

This guide takes you from `npm create` to a deployed app at `<your-app>.app.space`. You'll need a supported Node line (see [installation](/get-started/installation)) and a GitHub or Google account for sign-in.

For prerequisites and account setup, see [installation](/get-started/installation). For a tour of the files the scaffolder generates, see [project structure](/get-started/project-structure).

## 1. Scaffold an app

The `create-deepspace` package bootstraps a new project with the SDK preinstalled, a [worker](/concepts/architecture) wired to a [Durable Object](/concepts/architecture#durable-objects), and a Vite-powered React frontend.

```bash
npm create deepspace@latest my-app
cd my-app
```

The scaffolder is non-interactive by default and prints a list of files it created. Pass `--interactive` if you want a prompt-driven wizard.

If you are already signed in, the last thing the scaffolder does is register the app under **that** account - it names the account and the plane as it goes. See [which account the app registers under](/get-started/installation#which-account-the-app-registers-under) if more than one login could be active on this machine, or if you want to scaffold now and register later with `--no-register`.

**App names are globally unique.** The name becomes your subdomain at `<name>.app.space`, so it is claimed across all of DeepSpace - `my-app` in these examples is already taken. Pick something distinctive. A name someone else holds is refused at deploy time with `The name <host> is taken by another app.`

The name must be lowercase alphanumeric with optional dashes, 2-63 characters. You can rename later by editing `wrangler.toml` and deploying with `--rename`; your data, secrets, and collaborators travel with the app because they key to its immutable `DEEPSPACE_APP_ID`, not its name.

## 2. Start the dev server

```bash
npx deepspace dev start
```

This runs Vite and the worker together and opens `http://localhost:5173` with hot-module reload. The CLI also writes an app-owner JWT and platform URLs to `.dev.vars`, so your local worker can call the live auth, storage, and integration services.

Pass `--port` to run multiple apps in parallel:

```bash
npx deepspace dev start --port 5180
```

## 3. Sign in to the CLI

You'll need a DeepSpace account to deploy. One-time setup:

```bash
npx deepspace auth login
```

This opens your browser for GitHub or Google OAuth and stores a long-lived session at `~/.deepspace/session`. DeepSpace accounts are OAuth-only - there is no password. The same session covers every app on the machine, and every later command refreshes from it on its own.

Check status anytime with:

```bash
npx deepspace auth whoami
```

Running from CI or an agent with no browser? Log in here, then hand that environment your `~/.deepspace/session` file - it acts as you. See [agents and CI](/cli-reference/overview#agents-and-ci).

Manage your account, deployed apps, and billing at [dashboard.deep.space](https://dashboard.deep.space).

## 4. Make it yours

Open the project in your editor.

The scaffold already installs the [DeepSpace skill](https://github.com/deepdotspace/deepspace-skill) into `.agents/skills/deepspace/` and ships `AGENTS.md` + `CLAUDE.md` pointing coding agents at it. Claude Code, Cursor, and other agents pick up accurate type signatures and patterns with no extra setup. The pinned revision is recorded in `skills-lock.json`.

Key files:

* `src/pages/index.tsx` - the static landing page at `/`. Edit the hero copy.
* `src/pages/(app)/home.tsx` - the signed-in home route at `/home`.
* `src/schemas.ts` - collection schemas. Add a new collection alongside the seeded `users` and `settings`.
* `src/pages/(app)/_layout.tsx` - the provider stack. Auth and realtime data are already wired.
* `worker.ts` - the Hono worker. Add custom routes here.
* `index.html` - change the `<title>` and `data-theme` attribute to pick a preset.

### Where a page goes decides what it can do

Routing is file-based, and the folder a page sits in determines which providers wrap it. There are three tiers:

| Location                            | Providers                                  | Use for                                                                                            |
| ----------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `src/pages/*.tsx`                   | None                                       | Static pages - marketing, legal, docs. No auth fetch, no WebSocket.                                |
| `src/pages/(app)/*.tsx`             | `DeepSpaceAuthProvider` + `RecordProvider` | Dynamic pages. `useAuth`, `useQuery`, `useMutations`, presence all work. Sign-in **not** required. |
| `src/pages/(app)/(protected)/*.tsx` | The above, plus `<AuthGate>`               | Pages that require sign-in.                                                                        |

Folders in parentheses are [generouted](https://github.com/oedotme/generouted) route groups - they apply a layout without appearing in the URL. So `(app)/(protected)/todos.tsx` serves at `/todos`.

**The nesting is what gates the page, not the folder name.** `(protected)` has no special meaning to the router - it is gated only because `src/pages/(app)/(protected)/_layout.tsx` wraps its children in `<AuthGate>`.

Creating your own `src/pages/(protected)/` at the top level makes a *different* group with no `_layout.tsx` in it. Such a page is **not** behind `<AuthGate>` - it is publicly reachable - and it also sits outside `(app)/_layout.tsx`, so `useQuery` and `useMutations` have no `RecordProvider` and will fail. Put gated pages under the existing `src/pages/(app)/(protected)/`.

Try adding a `todos` [collection](/concepts/data-model). For the full schema reference, see [worker schemas](/sdk-reference/worker/schemas).

```ts
// src/schemas/todos-schema.ts
import type { CollectionSchema } from 'deepspace/schema'

export const todosSchema: CollectionSchema = {
  name: 'todos',
  columns: [
    { name: 'title', storage: 'text', interpretation: 'plain' },
    { name: 'completed', storage: 'number', interpretation: { kind: 'boolean' } },
  ],
  permissions: {
    member: { read: true, create: true, update: 'own', delete: 'own' },
    admin: { read: true, create: true, update: true, delete: true },
  },
}
```

```ts
// src/schemas.ts
import type { CollectionSchema } from 'deepspace/schema'
import { todosSchema } from './schemas/todos-schema'
import { usersSchema } from './schemas/users-schema'
import { settingsSchema } from './schemas/admin-schema'

export const schemas: CollectionSchema[] = [usersSchema, settingsSchema, todosSchema]
```

Then read and write from the UI using [`useQuery`](/sdk-reference/client/records#usequery-t-collection-options) and [`useMutations`](/sdk-reference/client/records#usemutations-t-collection):

```tsx
// src/pages/(app)/(protected)/todos.tsx
import { useQuery, useMutations } from 'deepspace'

type Todo = { title: string; completed: boolean }

export default function TodosPage() {
  const { records, status } = useQuery<Todo>('todos', { orderBy: 'createdAt' })
  const { create, put, remove } = useMutations<Todo>('todos')

  if (status === 'loading') return <p>Loading…</p>

  return (
    <>
      <ul>
        {records.map((r) => (
          <li key={r.recordId}>
            <input
              type="checkbox"
              checked={r.data.completed}
              onChange={(e) => put(r.recordId, { completed: e.target.checked })}
            />
            {r.data.title}
            <button onClick={() => remove(r.recordId)}>Delete</button>
          </li>
        ))}
      </ul>
      <button onClick={() => create({ title: 'New todo', completed: false })}>
        Add
      </button>
    </>
  )
}
```

The page lives at `/todos` - generouted picks it up automatically. Because it sits under `(app)/(protected)/`, it inherits `<RecordProvider>` from `(app)/_layout.tsx` (so the hooks above work) and `<AuthGate>` from `(app)/(protected)/_layout.tsx` (so it requires sign-in). See [authentication](/guides/authentication). Save and the page hot-reloads.

## 5. Deploy

Commit first - a deploy records the commit it ships:

```bash
git add -A && git commit -m "add todos"
npx deepspace deploy
```

The CLI bundles the worker, uploads it to Cloudflare Workers for Platforms, syncs any secrets from `.dev.vars`, and registers your subdomain. The [deployment](/concepts/deployment) reference covers the full pipeline. Within a few seconds, your app is live at:

```
https://<your-app>.app.space
```

An uncommitted worktree is refused with `dirty_worktree`. WIP commits are fine. If you genuinely want to ship without recording source lineage, pass `--no-push`.

Visit the URL, sign in, and add a todo. Open the same URL in a second tab - edits sync between them instantly.

## Next steps

You have a working app. Where to go from here:

* [Authentication](/guides/authentication) - pick a public, gated, or mixed model.
* [Collaborators](/guides/collaborators) - let a teammate deploy this app.
* [Presence and cursors](/guides/presence-and-cursors) - show who's online and broadcast cursor positions.
* [Payments](/guides/payments) - subscriptions and one-time products via Stripe.
* [AI chat](/guides/ai-chat) - streamed Claude or GPT with tool use over your records.
