> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goosybear.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Bringing your own site

> Send us an existing project instead of building inside Goosy: what the archive should contain, how dependencies work, the deploy manifest, migrations, media, and the check-then-ship sequence.

You do not have to build a site inside Goosy. Send us a project you already
have — a plain static folder, an Astro project, a Next.js project, or a Vite
app with its own backend — and we build and host it for you. This page is
what to prepare before you send it, and what happens once you do.

<Note>
  This page describes the **API path**: sending a project with a key, the way
  a developer or a coding agent would. See [Pages API](/api/pages) for the
  full call reference and [The deploy contract](/api/deploy-contract) for
  every field a project can declare.
</Note>

## What the archive should contain

Send a real project — `package.json`, your source files, and your lockfile —
zipped from the project root. A few things to leave out:

* **Build output and dependency folders.** `node_modules`, `.next`, `dist`,
  and any local emulator state don't belong in the archive — they're rebuilt
  for you, and including them only makes the upload slower and harder to
  read.
* **Your own copies and version-control files.** Leave out `.git` and any
  backup or "original" copies of the project sitting alongside it.
* **Content.** Your database rows and media files are not part of the code
  archive — see [Media](#media) and [Migrations for a new
  site](#migrations-for-a-new-site) below for how those travel separately.

## Dependencies: no install scripts, keep the lockfile

Two things make a project buildable here, and both are worth checking before
you send it:

* **No install scripts.** Dependencies are installed with lifecycle scripts
  turned off, so a package that only finishes setting itself up through a
  `postinstall` step won't finish setting itself up. Most everyday packages —
  UI libraries, routing, forms, styling — need nothing like this. If a
  dependency in your project does, it needs to come out before you send us
  the project.
* **Commit your `pnpm-lock.yaml`.** We build with **pnpm**, and we install
  exactly what that lockfile lists — we never resolve a version off the
  package registry on your behalf. Keep it committed and current: run `pnpm
  install` locally after any dependency change, so the lockfile reflects what
  `package.json` asks for. We read only `pnpm-lock.yaml`; an `npm`, `yarn` or
  `bun` lockfile isn't read at all, so a project that ships one of those
  instead — or no lockfile — is refused before anything is built, naming
  exactly what's missing.

We support a defined, growing set of everyday packages for the frameworks we
build — routing, UI primitives, forms, styling, and your database client
among them. If your project needs something we don't support yet, checking
your project first (see [the sequence](#check-it-then-ship-it) below) names
it for you, so you know before you spend a build finding out.

## The deploy manifest

Every project we host is read through one file, `pages.deploy.json` — see
[The deploy contract](/api/deploy-contract) for the complete field
reference. It says which paths your backend answers, and what it needs to be
wired to: a database, your own storage, secrets by name.

**You don't have to write it.** If your project doesn't include one, we look
at what you sent and write one for you, and hand it back on import so you
can see what we decided and commit it if you agree.

One part of it is worth knowing before you write your own: a route can be
marked so that only someone signed in to your workspace can reach it — the
same way `/admin` is commonly protected — and we enforce that before your
code ever runs. See [Members-only
routes](/api/deploy-contract#members-only-routes).

## Migrations for a new site

If your project needs its own database, declare a directory of numbered SQL
files in the manifest — `0000_create_table.sql`, `0001_add_column.sql`, and
so on — and they run in that order the first time your project is built.
Migrations are tracked one **file** at a time, not one statement at a time:
if a file fails partway through, the whole file is offered again on the next
build — so write every statement so it's safe to run again from the top.
`create table if not exists` is the simple version of that. `insert or
ignore` only gives you the same safety when the `id` column itself is
declared `primary key` or `unique` — a stable value alone isn't enough,
because without that declaration there's nothing for `or ignore` to conflict
on. Give every seeded row an explicit id of its own (never one your database
assigns automatically) **and** declare that column `primary key` or
`unique`, so a retried file skips a row it already inserted instead of
adding it twice.

Keep each file under 256 KB, keep the whole set to 64 files or fewer, and
keep any single statement well under 100,000 bytes — batch a large seed
across rows rather than one giant statement. The full list of what we check,
and the number beside what you sent, is in [the deploy contract's
ceilings](/api/deploy-contract#ceilings).

If you're replacing a site you already run with us, an already-applied file
has to match, byte for byte, what's already been applied there — add new
changes as new numbered files; don't edit one that already ran. **A new
file you add runs against your site's real, existing database as soon as
your replacement is confirmed and built — before you ever publish** (see
[Replacing a site you already run with us](#replacing-a-site-you-already-run-with-us)
below), so review a schema change the same way you'd review any change to
a live database.

## Media

There are two kinds of media, and they travel differently:

* **Files your pages serve directly** — images and assets your code
  references by path — ship inside the archive's static folder. They're
  classified as ordinary site files and go live with your first publish.
* **Files your backend reads from your own storage** are different. Your
  storage is created for you the moment your project is imported — not held
  back until you publish — and it starts out **empty**. There's no bulk
  import for existing media today. For a brand-new site, the honest path is
  to add files through your site's own upload flow once it's live, or ask us
  about a one-time bulk load.

## Check it, then ship it

Start with the same first step either way, then the path splits depending on
whether this site has a code project already.

**Validate first, always.** Send your archive to be checked without creating
anything — every problem is named in one pass, rather than one at a time
across repeated attempts.

### Bringing a new site

With no existing code project for this site, sending your archive **creates
the site and starts the build immediately** — there's no diff to review and
nothing to confirm, because nothing is being replaced.

1. **Import.** Creates the site and starts the build.
2. **Preview.** See below — [preview is live-bound, not
   read-only](#preview-is-live-bound-not-read-only).
3. **Publish.** Moves your live address to the build you just previewed — one
   exact version, reviewed before anything changes.

### Replacing a site you already run with us

Sending an archive to a site that **already has** a code project never
overwrites it outright — you see exactly what would change first, and
nothing is written until you say so.

1. **Import — see the diff first.** The response tells you nothing was
   written, and carries a **handle** together with a **diff** of what would
   change if you went ahead:

   ```json theme={null}
   {
     "outcome": "pending_confirmation",
     "siteId": "<site-id>",
     "handle": "h1.<sha256 of your archive>.<the current build>",
     "base": { "kind": "bundle", "ref": "<the current build>" },
     "diff": {
       "added": [{ "path": "about.html", "bytes": 1204 }],
       "removed": [],
       "changed": [{ "path": "index.html" }],
       "summary": { "added": 1, "removed": 0, "changed": 1, "unchanged": 4 }
     }
   }
   ```

   The diff names only code-project files — never your storage, your media,
   your domain, or anything else that makes the site yours. Your database is
   the one exception, and the rule is exact: a replacement whose migrations
   are byte-identical to what's already applied changes no data; a
   replacement that carries new migration files applies them to your live
   database at that same build, before you ever publish — see [Migrations
   for a new site](#migrations-for-a-new-site) above.

2. **Confirm — resend the identical archive, with the handle on the URL.**
   Send the exact same bytes to the exact same endpoint, adding the handle
   from step 1 as a query parameter:

   ```
   POST https://app.goosybear.ai/api/pages/ingest?confirm=<handle>
   ```

   Same `file`, same `siteId` — only the query string changes. That call is
   the one that actually writes: it answers with an `"outcome": "ingested"`
   body and starts the build.

3. **New migrations run against your real database, at that same build —
   before you ever publish.** If your replacement adds new migration files,
   this is where they run; see [Migrations for a new
   site](#migrations-for-a-new-site) above for what makes a migration safe to
   send this way.

4. **Preview.** See below.

5. **Publish.** Moves your live address to the build you just previewed.

### Preview is live-bound, not read-only

Your preview link is not a snapshot. The moment your project finishes its
build, its database and storage are already the same ones your published
site uses — there's no separate copy — so a preview and a live site share one
backend from the start. That makes preview a faithful check before you
publish, but it also means **a write you make while previewing is a real
write**: don't submit a form, upload media, or run an admin action against a
preview of a site you don't want to change yet.

### Republish to roll back

Every build you've shipped stays available, and putting a previous one back
live is the same act as publishing — you're just choosing an earlier build.
Rolling back reaches your **100 most recent builds**; an older one isn't
offered as a rollback target.

## Where to go next

* [Pages API](/api/pages) — the full call reference for each step above.
* [The deploy contract](/api/deploy-contract) — every `pages.deploy.json`
  field, and the complete ceilings list.
* [Site and project databases](/api/site-database) — how your code reads its
  database once it's live.
* [Recipes](/api/recipes) — a complete run, from an empty folder to a live
  site.
