Skip to main content
The git details behind the shipping workflow: what to commit, the one file the Xano SDK maintains for you, and what a reviewer checks that a normal code review misses.
A pull request is how a branch gets reviewed before it becomes part of main. Push the branch, then open one with the GitHub CLI:
Merging is a button on the pull request. For git itself, the GitHub “Hello World” guide takes ten minutes, and Learn Git Branching teaches the branch model interactively.

What goes in git

Commit

xano/** (your defs and xano/xano.lock), package.json, package-lock.json, and your frontend source.

Never commit

.xano/auth.json, .env, .secrets.json, node_modules, frontend/dist, and any exported bundle. A bundle carries your workspace’s environment variable values.
xanosdk login --local-auth adds .xano/auth.json to .gitignore for you. A scaffolded project already ignores the rest; if you built the repo by hand, this is the minimum:
A pull writes environment variable values to xano/.env and documentation tokens to xano/.secrets.json, both ignored above, and only the names to xano/.env.example. Keep it that way: never move a value into xano/workspace.ts or anything else under xano/. Anything you commit there is in your git history permanently.

xano.lock, and why it is in the repo

xano/xano.lock freezes your workspace’s object identities: the guids a release matches on, plus the canonical URL tokens of API groups and toolsets. It is what makes a release update in place instead of duplicating. It is worth understanding when it is merely convenient and when it is irreplaceable, because that changes how carefully you treat a conflict: You generally will not know which entries have diverged without looking, and a workspace adopted wholesale from the UI is almost entirely divergent. So: commit it, always. It also holds two records the code cannot rebuild: landed, what this project landed on each workspace and tenant (what deploy --to … --prune deletes by), and synced, the baseline workspace diff uses to say which side changed.
npm run xano:check in CI (export --check --strict, which implies --frozen-lock) fails the build if an export would change the lock, which catches someone who forgot to commit it, and stops a canonical minted in a throwaway container from silently changing your public URLs. Mint locally, commit the lock.

Resolving a xano.lock conflict

This is the most common merge conflict in a Xano SDK repo, and it is almost always benign: two branches each added entries, and git cannot tell that the additions are independent. A conflict is git telling you two branches changed the same lines and it will not guess which wins. It marks the spot in the file and waits for you:
Here both entries are wanted: they describe different objects. Resolve the file as the union of both sides:
  1. Under objects, keep every entry from each side.
  2. Under landed, keep every entry from each side.
  3. Under synced, keep either side’s baseline for a Xano branch, or delete it. A missing baseline only means workspace diff cannot say which side changed until the next deploy or pull.
  4. Delete the markers and fix the commas.
Do not take one side with git checkout --ours or --theirs. It throws the other branch’s entries away, and for an adopted or renamed object nothing in your code can bring them back. Every command refuses a lock that still holds conflict markers, duplicate keys or broken JSON, and says how to fix it.
Resolve the code conflict too, keeping both branches’ defs. If one side’s defs were dropped in the merge, xano:check fails on their lock entries, which no exported object matches. That is the signal to restore the defs, not to delete the entries.

Making git resolve it for you

Git’s merge=union driver keeps both sides’ lines instead of raising a conflict. It works line by line, not by JSON key, so on xano.lock it can leave a missing comma, or the same synced key twice, and every command refuses that lock until it is fixed by hand. Resolve the conflict yourself with the recipe above.
Do not use merge=ours or merge=theirs either, because either silently discards one branch’s identities, which for an adopted or renamed object is unrecoverable.

Renames

Renaming an object in code looks like a delete plus a create to the engine, because the guid derives from the name. Moving the lock entry is what keeps it a rename:
Commit the resulting xano/xano.lock in the same PR as the rename. A rename whose lock change is committed separately is a delete-and-create in whichever release lands first.

Two people renaming on different branches

The renames are independent (different entries), so this resolves like any other lock conflict: take the union of both sides. What breaks it is a rename that reached main without its lock entry; the second branch then exports against a lock that no longer describes reality. If an export warns about an orphaned entry after a merge, that is the signal: run lock rename for the object it names before releasing. If both branches renamed the same object (one to stock, the other to goods), the union holds one guid under two keys, and every command refuses it, naming both. Keep the name your code exports and drop the other key. The guid stays under the name you keep:
Never resolve a rename conflict by deleting the entry. The guid it holds is the only link to the live object; without it the next release creates a duplicate and leaves the original orphaned in your workspace.

Reviewing a Xano SDK pull request

Review the TypeScript like any other code, plus four things a normal code review misses, because they are invisible in a diff that looks routine.
1

A removed table column, or a removed table

This is data loss on release. A tenant deploy replaces the tenant with the release, and on a workspace tables are shared by every Xano branch, so either way the data is gone as soon as the release lands. A tenant deploy names what it drops in a confirmation that --yes skips, and promote refuses until someone adds --allow-shared-schema-changes. In CI, neither stops anyone. Catch it in review.
2

Unexpected churn in xano.lock

Expected: new entries for new objects; a moved entry for a rename; new landed and synced entries after a landing. Unexpected: entries disappearing, or a guid changing for an object nobody touched. Either means an identity is about to be lost, so ask before approving.
3

Secrets in the workspace config

Environment variable names belong in workspaceConfig({ env }) with an empty value; the values live in the gitignored xano/.env. A PR that writes a real value into the config is adding it to your git history permanently.
4

The ephemeral URL actually works

The PR job leaves a live backend and frontend behind. Click it. A diff cannot tell you the thing runs.
Run the plan before approving anything that touches a table:
It sends nothing. It prints what this change would do to production, including every column it would drop.

Did that release land?

Worth knowing when a CI job or a rerun leaves you unsure. Do not re-run a release write blind: a release create, tenant deploy or promote whose answer was lost exits 9 and prints a resolveWith command, and a retried release create can leave a second release under a suffixed name. Run the resolveWith command, then look:
To see whether the workspace runs what your code says, compare the two:
Add --branch <label> to compare a Xano branch that is not live. Under --json, the differences are split by which side moved since they last matched (the baseline under synced in the lock): changedThere (edited in Xano), changedHere (edited in this project), and changedBoth (edited on both sides, a conflict to reconcile by hand). deploy --to workspace reads the same baseline before it writes. It refuses to overwrite an object that changed in Xano (exit 2, SDK_WORKSPACE_CHANGED) until you pull the change into the project, or pass --overwrite to deploy over it. More in Drift.

Next

Rollback and recovery: what to do when a release that landed has to come back out.