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

# Repo is the truth

> The flow where TypeScript is authoritative: deploy to an ephemeral, review the diff in a pull request, and release main to production — solo, as a team, or from CI.

Your TypeScript is authoritative. The workspace is a deployment target that `xanots release`
reconciles to `main`.

This is one flow, not three. **Solo is the base**; a team adds review, and CI adds a machine
that runs the same commands. The commands themselves never change.

New here? Read [Development workflows](/guides/development-workflows) first — it covers the
choice between this and letting the workspace be authoritative, and what a Xano branch does
and does not isolate.

<Note>
  **Git is the ideal path, not the only one.** The Xano builder stays fully usable — open it any
  time to look at what is live, debug an endpoint, or read request history. What changes is
  *writing*: an edit made in the builder is invisible to your code, so your next release does
  not know about it. That is recoverable, and in an emergency it is exactly what you should do —
  see [When you have to work outside git](#when-you-have-to-work-outside-git) below, and read it
  **before** you need it.
</Note>

## Day one

Two different starting points, and only one of them is irreversible.

<Tabs>
  <Tab title="A new project">
    Nothing exists yet, so nothing can be lost.

    ```bash theme={null}
    xanots login                                   # pick the instance + workspace at consent
    xanots init my-app && cd my-app
    git init && git add -A && git commit -m "scaffold"
    ```

    A project from `xanots init` is locked from its first export, so `xano.lock` is created
    and maintained for you. See [Git, locks & merges](/guides/git-and-merges) for what else
    belongs in that first commit.
  </Tab>

  <Tab title="A workspace that already exists">
    You are **adopting** a live workspace into a repo. The engine assigned its objects random
    guids, and nothing in your code can re-derive them — so capture them before you ever
    release.

    ```bash theme={null}
    xanots init my-app --from workspace
    cd my-app && git init && git add -A && git commit -m "adopt workspace"

    xanots workspace export --path live.json
    xanots lock import live.json --entry=xano/index.ts --yes
    git add xano.lock && git commit -m "pin live identities"
    ```

    <Warning>
      Do the `lock import` **before** the first release. Without those pinned identities a
      release matches nothing and creates a duplicate of every adopted object instead of
      updating it.
    </Warning>

    Read `xano/README.md` — it lists anything that did not translate cleanly — and read
    `xano/workspace.ts`, which carries your environment variable **values** inline. Move real
    secrets out before that first commit. Then tell the team where the truth now lives: changes
    are made in code and released, not typed into the builder. Emergencies are still
    emergencies — [reconcile them back](#when-you-have-to-work-outside-git) the same day.
  </Tab>
</Tabs>

## The loop

<Steps>
  <Step title="Branch, and deploy to your own ephemeral">
    ```bash theme={null}
    git switch -c orders-api
    # edit xano/…
    xanots deploy ./xano/index.ts --static ./frontend/dist
    ```

    Every developer's `deploy` goes to **their own** ephemeral, so two people can be mid-change
    at once without touching each other or production. It is disposable and expires on its
    own, so a bad deploy costs you nothing. Deploy as often as you like.
  </Step>

  <Step title="Prove it">
    ```bash theme={null}
    xanots test run-all          # against the ephemeral you just deployed
    ```
  </Step>

  <Step title="Open a pull request">
    Push the branch, then open the PR on GitHub/GitLab from the URL the push prints:

    ```bash theme={null}
    git add -A && git commit -m "add orders API"
    git push -u origin orders-api
    ```

    Paste the ephemeral's URL into the PR description. A reviewer gets a running backend *and*
    frontend to click through, not just a diff.

    ```bash theme={null}
    xanots ephemeral list                      # what everyone has up
    xanots ephemeral delete <tenant> --yes     # clean one up early
    ```

    Review the TypeScript like any other code — plus the four things a normal code review
    misses, which are covered in [the review checklist](/guides/git-and-merges#reviewing-a-xanots-pull-request).

    <Note>
      **Working solo?** Skip this step and commit straight to `main` — everything else on the
      page is unchanged. **New to git or pull requests?** [Git, locks &
      merges](/guides/git-and-merges) opens with the eight commands these flows use and how a
      PR actually works.
    </Note>
  </Step>

  <Step title="Merge, then release from main">
    One person — or the pipeline below — releases:

    ```bash theme={null}
    git switch main && git pull
    xanots release ./xano/index.ts --dry-run   # read the plan first
    xanots release ./xano/index.ts
    git add xano.lock && git commit -m "release"
    ```
  </Step>
</Steps>

<Tip>
  **Always `--dry-run` first.** It prints exactly which objects would be created, updated, or
  dropped — including any table column your schema no longer defines, which takes its data with
  it. See [what a release changes](/guides/deploying#deploy-targets-and-what-a-release-changes).
</Tip>

## Automating it

The same commands, run by a machine. Any runner works — the requirement is three environment
variables, the credential shape that never expires or rotates:

```bash theme={null}
XANO_INSTANCE_URL=https://your-instance.xano.io
XANO_WORKSPACE_ID=3
XANO_META_TOKEN=your-meta-api-token
```

<Warning>
  Never run `xanots login` in a pipeline — it blocks on browser consent. And set all three
  variables **together**; setting some is a hard error naming the rest, not a quiet fallback to
  whatever credential happens to be on the runner.
</Warning>

### On every pull request

Build it, prove it, leave a live URL behind:

```bash theme={null}
npm ci
npx xanots export ./xano/index.ts --strict --frozen-lock
npm run build
npx xanots deploy ./xano/index.ts --static ./frontend/dist --test
```

* `--strict` turns build warnings into failures.
* `--frozen-lock` fails if the export would *change* `xano.lock`, which catches someone who
  forgot to commit it.
* `--test` runs the deployed tests; a failing suite exits **5**.

### On merge to main

```bash theme={null}
npm ci
npx xanots release ./xano/index.ts --yes
```

Safe to run on every merge, and safe to re-run. When the workspace already matches the code,
nothing is sent at all — the summary reports `"upToDate": true` and no `updated_at` moves.
`--yes` skips the confirmation prompt but never skips the printed preview, so the log still
shows what changed.

<Note>
  `--prune` (delete objects the project no longer defines) is deliberately absent here.
  Deletions are worth a human reading the plan: `xanots release ./xano/index.ts --prune --dry-run`.
</Note>

### A staged pipeline

<Warning>
  **Not available yet.** Branch releases are still rolling out instance-side, so this pattern is
  a preview — the flags parse and run, but on an instance without support the release refuses
  rather than staging. Do not build a pipeline on it today; the rest of this page needs none of
  it.
</Warning>

Once it lands, you will be able to put the merge on a branch and make promotion a separate,
human button:

```bash theme={null}
npx xanots release ./xano/index.ts --branch staging --backup-branch --yes
# …someone looks at it in the builder…
npx xanots workspace branch set-live staging --yes
```

Note that a branch stages **logic** and shares **schema** — see
[what a Xano branch isolates](/guides/development-workflows#what-a-xano-branch-actually-isolates).

**Until then**, the equivalent human gate is a manual approval on the release job itself: run
`--dry-run` on merge, have someone read the plan, then run the release.

### Nightly, or before a release

Check the code against a real engine without touching anything you own. `preflight` creates
its own throwaway environment, imports into it, diffs the round trip, and deletes it again:

```bash theme={null}
XANO_VALIDATE_INSTANCE=… XANO_VALIDATE_TOKEN=… npx xanots preflight ./xano/index.ts --runtime
```

## Ready-to-paste pipelines

Same four commands either way — only the YAML around them differs.

<AccordionGroup>
  <Accordion title="GitHub Actions">
    ```yaml theme={null}
    # .github/workflows/xano.yml
    name: xano
    on:
      pull_request:
      push:
        branches: [main]

    env:
      XANO_INSTANCE_URL: ${{ secrets.XANO_INSTANCE_URL }}
      XANO_WORKSPACE_ID: ${{ secrets.XANO_WORKSPACE_ID }}
      XANO_META_TOKEN: ${{ secrets.XANO_META_TOKEN }}

    jobs:
      preview:
        if: github.event_name == 'pull_request'
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with: { node-version: 20, cache: npm }
          - run: npm ci
          - run: npx xanots export ./xano/index.ts --strict --frozen-lock
          - run: npm run build
          - run: npx xanots deploy ./xano/index.ts --static ./frontend/dist --test

      release:
        if: github.ref == 'refs/heads/main'
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with: { node-version: 20, cache: npm }
          - run: npm ci
          - run: npx xanots release ./xano/index.ts --yes
    ```

    To post the ephemeral's URL as a PR comment, capture the deploy's stdout — it is a
    projected, secret-free JSON summary carrying `baseUrl` — and feed it to
    `actions/github-script`. The raw workspace blob is never printed, so nothing sensitive
    reaches the log.
  </Accordion>

  <Accordion title="GitLab CI">
    ```yaml theme={null}
    # .gitlab-ci.yml
    default:
      image: node:20
      before_script:
        - npm ci

    variables:
      XANO_INSTANCE_URL: $XANO_INSTANCE_URL
      XANO_WORKSPACE_ID: $XANO_WORKSPACE_ID
      XANO_META_TOKEN: $XANO_META_TOKEN

    preview:
      rules: [{ if: $CI_PIPELINE_SOURCE == "merge_request_event" }]
      script:
        - npx xanots export ./xano/index.ts --strict --frozen-lock
        - npm run build
        - npx xanots deploy ./xano/index.ts --static ./frontend/dist --test

    release:
      rules: [{ if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH }]
      script:
        - npx xanots release ./xano/index.ts --yes
    ```

    Mark all three variables **masked** and **protected** in the project's CI/CD settings.
  </Accordion>

  <Accordion title="Any other runner">
    There is nothing runner-specific in the flow — three environment variables and four
    commands. On a PR:

    ```bash theme={null}
    npm ci
    npx xanots export ./xano/index.ts --strict --frozen-lock
    npm run build
    npx xanots deploy ./xano/index.ts --static ./frontend/dist --test
    ```

    On merge to the default branch:

    ```bash theme={null}
    npm ci
    npx xanots release ./xano/index.ts --yes
    ```

    Exit codes are the contract: `--test` exits **5** on a failing suite, and a static failure
    after a committed backend deploy exits **3** with a resumable message.
  </Accordion>
</AccordionGroup>

## When a release goes wrong

Your code is the record of what production should be, so rolling back is **releasing an
earlier version of it**. There is no separate undo command — and that is a feature, because it
means the rollback goes through the same reviewed, repeatable path as everything else.

<Steps>
  <Step title="Get main back to the last good state">
    ```bash theme={null}
    git switch main && git pull
    git revert <the-bad-commit>     # or: git revert HEAD
    ```

    `revert` makes a *new* commit undoing the old one, so the history stays honest and the
    rollback is itself reviewable.
  </Step>

  <Step title="Read the plan before you send it">
    ```bash theme={null}
    xanots release ./xano/index.ts --dry-run
    ```

    Check it says what you expect. A rollback is still a release — it can drop a column just
    as easily as the change that caused the problem.
  </Step>

  <Step title="Release">
    ```bash theme={null}
    xanots release ./xano/index.ts
    ```
  </Step>
</Steps>

### What a rollback does and does not restore

|                                                   | Comes back?                                                                                     |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Endpoints, functions, tasks, triggers — **logic** | **Yes.** Re-released from the reverted code.                                                    |
| A table **column you added**                      | **Yes** — but it comes back empty.                                                              |
| A table **column you dropped**, and its data      | **No.** Releasing the old schema recreates the column; the values that were in it are gone.     |
| Table **rows**                                    | Unaffected either way — a release never writes rows unless you pass `--seed` or `--reset-data`. |
| An **environment variable** value                 | Unaffected — env vars are add-only on a merge.                                                  |

<Warning>
  **Dropped column data is not recoverable by re-releasing.** This is the one failure a rollback
  cannot fix, and it is why the release preview names every column it is about to drop and asks
  first. If you are ever unsure, answer no and read the plan again — a refused release costs a
  minute; a dropped column costs the data.
</Warning>

<Tip>
  **Restore from a backup, not from a rollback**, when data is involved. Your Xano workspace's
  own backups are the recovery path for lost rows or columns — the release pipeline only ever
  reconciles *structure and logic* to your code.
</Tip>

### If you are not sure whether the release even landed

Run it again and read the plan. A release compares before it sends, so when the workspace
already matches your code, **nothing is sent at all** — the plan prints `no changes`, the
summary reports `"upToDate": true`, and no `updated_at` moves. Re-running is safe and is the
fastest way to find out where you stand.

## When you have to work outside git

Production is down at 2am, or someone fixed a typo in the builder because it was thirty
seconds' work. This happens on real teams, and doing it breaks nothing permanently — **as long
as you bring the change back into git before your next release.**

### What is safe, and what is not

| In the Xano builder                                            | Safe?                                                                                                                                          |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Looking at anything — endpoints, tables, request history, logs | **Yes.** Reading never diverges.                                                                                                               |
| Running an endpoint to debug it                                | **Yes.**                                                                                                                                       |
| Changing an **environment variable** value                     | **Yes**, and it is the *correct* place — a release will not overwrite it, because env vars are add-only on a merge.                            |
| Editing table **rows** (your data)                             | **Yes.** A release never writes rows unless you pass `--seed` or `--reset-data`.                                                               |
| Editing **logic** — an endpoint, function, task, trigger       | **Recoverable**, not safe. It is invisible to your code until you pull it back.                                                                |
| Editing a **table's schema** — adding or dropping a column     | **Recoverable, and the riskiest.** Your code still describes the old shape, so the next release is planned against a schema you did not write. |

<Warning>
  **The danger is not the edit — it is the next release.** An ordinary `xanots release` merges,
  so it will not delete an object someone added in the builder. But `--prune` deletes objects
  this project released and no longer defines, and a column your code does not describe can be
  dropped by a release whose plan looks routine. **Always `--dry-run` and read the plan** after
  anyone has been working in the builder.
</Warning>

### Bringing the change back

Reconcile the same day — the risk grows the longer the two copies disagree.

<Steps>
  <Step title="Pull the live workspace over your checkout">
    ```bash theme={null}
    git switch -c hotfix-from-builder
    xanots init . --from workspace
    ```
  </Step>

  <Step title="Read the diff — this is the whole point">
    ```bash theme={null}
    git diff
    ```

    You should see **only** the hotfix. If you see more, someone else has been editing in
    there too — see below.
  </Step>

  <Step title="PR it like any other change">
    Now the fix exists in code, in review, and in history — and your next release knows about
    it.
  </Step>
</Steps>

<Tip>
  Doing it deliberately? Make the fix in the builder, then pull it back and open the PR while
  you still remember what you changed. The hotfix nobody reconciles is the one that gets
  silently reverted three weeks later.
</Tip>

### If this keeps happening

One emergency hotfix a quarter is normal. If the diff regularly shows work you did not expect,
that is data rather than a discipline problem: people are choosing the builder because it
suits how they work.

At that point the repo is not really your source of truth. The arrangement that fits is
[Workspace is the truth](/guides/workflow-workspace) — builder edits as the *expected* path,
with Xano branches as the merge — but the branch releases it needs are **still in
development**, so it is not adoptable yet.

Until it lands, the practical answer is to make reconciling routine rather than exceptional:
pull the workspace back on a schedule (a weekly `xanots init . --from workspace` on a branch,
reviewed and merged like any PR) so drift is caught in days rather than discovered by a
`--prune`.

## Other arrangements

<AccordionGroup>
  <Accordion title="A preview environment per pull request">
    Nothing extra to configure: the PR job above already deploys to an ephemeral and prints
    the URL. Post it as a PR comment and let it expire on its own, or tear it down on merge:

    ```bash theme={null}
    xanots ephemeral list
    xanots ephemeral delete <tenant> --yes
    ```
  </Accordion>

  <Accordion title="An AI builder (Bolt, Lovable) driving the backend">
    The agent authors TypeScript and deploys to ephemerals, so it slots in as a contributor:
    it works on a branch, you read the diff, you merge. Keep `xanots release` off the agent's
    path — let it deploy freely and let a human release. See [Bolt](/connectors/bolt) and
    [Lovable](/connectors/lovable).
  </Accordion>

  <Accordion title="Several backends in one repo">
    Each entry file is its own workspace and gets its own credential, its own `xano.lock`
    beside it, and its own release. Point every command at the entry explicitly:

    ```bash theme={null}
    xanots deploy ./services/orders/index.ts
    xanots release ./services/billing/index.ts --dry-run
    ```

    `lock rename` and friends look for `xano.lock` in the *current directory* unless you pass
    `--entry=<path>`, so always pass it in a multi-backend repo.
  </Accordion>
</AccordionGroup>
