Skip to main content
Everything here applies where your repo is the truth. If your workspace is authoritative, the working copy is disposable and git is not involved — see Workspace is the truth.
This page assumes you have used git before. If you have not, here is the whole vocabulary you need for XanoTS, in the order you will meet it. Nothing else on this page uses a command that is not in this list.The loop is: switch → edit → add → commit → push. Repeat as often as you like; a commit is cheap and local until you push.A branch is the unit of work. You start one per change (git switch -c add-orders-api), commit to it freely, and it never affects main until it is merged. That is what makes it safe to experiment.A pull request (GitHub) or merge request (GitLab) is how a branch gets reviewed before it becomes part of main. It is not a git command — it happens on the website:
1

Push your branch

The -u origin <name> part is only needed the first time you push a given branch; after that, plain git push works. The output prints a URL — open it and you land on the “create pull request” (GitHub) or “create merge request” (GitLab) page with everything filled in.
2

Describe it, and paste your ephemeral URL

Say what changed and why. Paste the URL xanots deploy printed, so your reviewer can click through a running copy instead of reading a diff.
3

Someone reviews it, then it merges

They comment or approve; you push more commits to the same branch if needed. Merging is a button on that page. Afterwards, get the merged work locally:
Both hosts have a CLI that skips the website entirely:
Install: cli.github.com, then gh auth login once.
Working alone? You can skip branches and pull requests entirely — commit straight to main. Everything else on this page still applies, because xano.lock conflicts and renames come from merging, which you are not doing. Come back when someone joins you.For anything beyond this, the GitHub “Hello World” guide is 10 minutes and covers branches and pull requests properly, and Learn Git Branching teaches the branch model interactively.

What goes in git

Commit

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

Never commit

.xano/auth.json, .env, node_modules, frontend/dist, and any exported bundle — a bundle carries your workspace’s environment variable values.
xanots login --local 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 pulled xano/workspace.ts carries environment variable values inline, because that is what a deploy sends. Read it before the first commit and move real secrets out — into backend environment variables read at runtime with env(name). Anything you leave there is in your git history permanently.

xano.lock, and why it is in the repo

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.
--frozen-lock in CI 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 XanoTS 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. And you do not have to get the merge right by hand, because xanots export rebuilds this file from your source:
--ours sounds like it throws the other branch’s entries away, and on any other file it would. It is safe only here, and only because of the export two lines below: that command regenerates the whole lock from the merged code, so whatever you pick now is discarded either way. Its only job is to get the conflict markers out of the file.
The export is the authority, not your hand-edit. It walks the merged source and writes every entry it needs — so as long as both branches’ code made it into the merge, both branches’ entries come back.
Resolve the code conflict first, then export. If you export while xano/ still has conflict markers in it, the build fails; if you export having accidentally dropped one side’s defs, the lock is rewritten without their entries and the mistake is now committed in two places.

Making git resolve it for you

Because the export can always rebuild the file, xano.lock is a reasonable candidate for a union merge — git keeps both sides’ lines instead of raising a conflict, and your next export tidies it:
This trades a conflict for a possibly-messy file that the next xanots export normalizes. Do not use merge=ours or merge=theirs — 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.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 both sides and re-export. 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.
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 XanoTS 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, and no flag is required for it to happen. The release preview names each column that would be dropped and asks — but by then the PR is merged. Catch it in review.
2

Unexpected churn in xano.lock

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

Secrets in xano/workspace.ts

Environment variable values live inline there. A PR that adds one 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 just prints exactly what merging this would do to production.

A release is safe to re-run

Worth knowing when a merge or a rerun leaves you unsure whether something landed. Before importing, release compares the bundle against the workspace object by object; when everything already matches, nothing is sent — the plan prints no changes, the summary reports "upToDate": true, and no updated_at moves. So the recovery for “did that release actually happen?” is to run it again and read the plan.