From zero to your module in your sandbox
From an empty folder to your module rendering inside Building OS, in a sandbox of your own (or one you share with a colleague: see Two developers, one sandbox). It takes about 30 minutes of your own time, plus one wait on us after step 3 while we set up your sandbox sign-in; carry on with steps 4 and 5 in the meantime. The steps are in order because each one needs the one before it, and each links to the page that covers it in depth.
Before you start
You need four things. If any is missing, write to the contact named in your welcome message, by email or Telegram; developers@tango.vision reaches the same people. We answer the same business day, Moscow time.
- A developer account in the
developersrealm. Registration is invite-only; we create the account and you set your own password. You use it to mint API keys in the portal. See Developer account. - A registry token for
npm.k8s.tangovision.dev. The SDK lives on a private registry, and the token is personal to you. It arrives in a separate message from your contact, not in the account email. Never commit it. - A sandbox sign-in, issued after your sandbox exists (step 3). Invited developers get a username and a temporary password, separate from the developer account. Tango Vision employees sign in with their company account instead, once they are in the
/sandbox-developersgroup and an operator has linked their sandbox login; they get no password. See Sign in. - Node 24 (22 also works) and pnpm 10.18.2, the exact pnpm version the platform's CI uses. Chrome or Firefox for the last step: Safari cannot load a module from your machine.
One-time setup
Corepack ships with Node 24 and installs that exact pnpm:
corepack enable && corepack prepare pnpm@10.18.2 --activate
pnpm -v # expect 10.18.2If pnpm -v prints another version, a pnpm installed some other way (npm, Homebrew) comes first on your PATH; remove it or move it behind corepack's.
Then point the @tv scope at the private registry, reading the token from an environment variable so the file itself holds no secret:
# ~/.npmrc
@tv:registry=https://npm.k8s.tangovision.dev/
//npm.k8s.tangovision.dev/:_authToken=${TV_NPM_TOKEN}export TV_NPM_TOKEN=... # the registry token; keep it in your shell profile or a secret manager
npm view @tv/extension-sdk version # expect 1.15.1 or laterUse @tv/extension-sdk 1.15.1 or later; the output below is from 1.15.1. Older versions have bugs that break steps of this walkthrough, from a scaffold that does not build to a login that fails on Windows. If you already have a module from one of them, Known issues by SDK version lists what to fix by hand. Details, including the Docker build-secret pattern: Getting started.
1. Scaffold the module
npx @tv/extension-sdk init-module hello --category=operations --external --scope=acme --author="Acme Ltd"
cd tv-module-hello
pnpm installPut your company's npm scope in --scope and its name in --author. They give the module your identity: the id and package name become @acme/module-hello and the manifest author Acme Ltd. Without them the module is born as @tv/module-hello, authored by "Tango Vision", and @tv is our scope. Keep the module-<slug> part as generated: the shell's URL for your module is the part of the id after module-.
--external generates CI that runs on GitHub-hosted runners with your own TV_NPM_TOKEN secret. Without it, the generated workflow calls actions in a private Tango Vision repository and fails outside our organisation.
You get module-manifest.json (the contract: id, permissions, events, where the UI mounts), src/Shell.tsx (the component the shell renders), vite.config.ts (the federation remote exposing ./Shell) and a README naming the ports and URLs below.
pnpm dev # standalone preview with a mock platform context, http://localhost:5001
pnpm test
npx @tv/extension-sdk validate module-manifest.json
npx @tv/extension-sdk check-exposes module-manifest.json --config=./vite.config.tsEdit Shell.tsx and the locales/ until the standalone preview shows what you want. Inside a real shell only certain SDK calls work: useSelectedBuildingId() from @tv/extension-sdk/react for the selected building, and getAccessToken() or authorizationHeader() from @tv/extension-sdk/context to authenticate your API calls, once per request. Never read localStorage['access_token']. React context does not cross the federation boundary, so usePlatformContext() throws inside the shell.
In depth: Your first module and Calling the API from a module.
2. Get a sandbox API key
Open the developer portal, sign in with your developer account and mint a key. It is shown once. Keys carry the sandbox scope by default, expire after 90 days and can be revoked in the same place.
export TV_API_TOKEN=tvk_... # the portal key; or: npx @tv/extension-sdk loginKeep this key in TV_API_TOKEN for the rest of the walkthrough: step 5 uses it to look your sandbox up.
On Windows, login stores the session in a file under your user profile (~/.config/tv-sdk/credentials.json) and says so: the session is larger than Windows Credential Manager holds. SDK 1.15.0 and earlier fail there instead (Known issues). The portal key works everywhere.
In depth: Get a sandbox API key, API keys and CLI sign-in.
3. Create your sandbox
npx @tv/extension-sdk sandbox create \
--name=hello-dev --type=office --storeys=2 --area-sqm=5000 --ttl-days=30Provisioning takes one to a few minutes; the command polls until the sandbox is ready, then prints:
✓ Sandbox ready:
id: sbx_…
name: hello-dev
slug: <slug>
organizationId: <organization id>
status: ready
api: https://<slug>.sandbox.k8s.tangovision.dev
console: https://<slug>.sandbox.k8s.tangovision.dev
expires: 2026-10-26T…
Signing in to the sandbox shell needs sandbox logins, which Tango Vision sets up.
Send this line to your Tango Vision contact (or developers@tango.vision):
Please set up sandbox logins for slug=<slug> (organizationId=<organization id>)
Meanwhile, register your module (serve it first: pnpm dev:remote + pnpm serve:remote):
npx @tv/extension-sdk sandbox install module-manifest.json --sandbox=sbx_… --remote-entry=http://localhost:6001/assets/remoteEntry.js| Line | What it is |
|---|---|
id | sbx_…: your sandbox id, which step 5 takes |
slug | Your sandbox's slug: the start of its URL, and what your sandbox logins are set up for |
organizationId | Your sandbox's organisation; your sandbox login carries it |
api | The sandbox's own tv-api |
console | The same host: the sandbox shell you open in step 6 |
expires | When the sandbox and everything in it is deleted |
Please set up sandbox logins … | What you send us: see below |
npx … sandbox install … | Step 5's command with your id filled in, on one line so it pastes into any shell |
You do not need sandbox connect for this walkthrough: step 5 looks everything up from the id. connect prints the sandbox's own tv-api URL and administrator token as TV_API_URL=… / TV_API_TOKEN=… settings for server-side code only (a script, a local backend). Never put that token in browser code, and do not export the pair in the terminal you run sandbox commands from: sandbox list and create would then talk to your sandbox instead (see When something is wrong).
Inside: a seeded office building with storeys, spaces and equipment, its own PostgreSQL database and its own tv-api. The data is synthetic; keep it that way, and put no real company data in a sandbox unless we have agreed it in writing. Types: office, mall, university. Up to 3 sandboxes at a time.
Expiry. 30 days is the longest a sandbox can be created for; without --ttl-days it is 14. You can extend it twice, 7 days each, while it is still running:
npx @tv/extension-sdk sandbox extend sbx_... --days=7Nothing reminds you before it expires, so note the expires date. An expired sandbox cannot be extended: its database and host are deleted, and a new sandbox has a new slug, so the sign-in hand-off below happens again.
For a minute or two after ready, the sandbox's HTTPS certificate may not be issued yet. sandbox install then fails with Cannot reach https://<slug>.sandbox.k8s.tangovision.dev/…: fetch failed, and the browser warns that the connection is not private. Wait a minute and try again.
Now send us the sign-in line
Send the Please set up sandbox logins for slug=… (organizationId=…) line that create printed to the contact named in your welcome message, by email or Telegram; developers@tango.vision reaches the same people. We answer the same business day, Moscow time. We register the sandbox host for sign-in and create (or, for employees, link) your sandbox shell login. Until we do, the shell cannot sign you in, but steps 4 and 5 do not need it: carry on. Every time you create a new sandbox, this step repeats.
Lost the line? npx @tv/extension-sdk sandbox list shows each sandbox's slug and organizationId again. Send us those two values only: the --format=json output also carries each sandbox's apiToken, which stays secret.
In depth: On-demand sandboxes, including what is isolated and what is shared, the quotas, and the API behind this command.
4. Serve the remote from your machine
A federation remote exists only in a build; pnpm dev emits no remoteEntry.js. Two terminals, started in this order:
pnpm dev:remote # vite build --watch
pnpm serve:remote # vite preview --port 6001 --strictPort, CORS onUntil the first build has finished, serve:remote stops with The directory "dist" does not exist. Did you build your project?; start it again once dev:remote has built.
Your remote is at http://localhost:6001/assets/remoteEntry.js. The port is fixed on purpose: if 6001 is busy the command stops with Port 6001 is already in use instead of silently moving, which would leave the registry pointing at nothing. Use localhost, not 127.0.0.1: on macOS the preview server answers on localhost only.
In depth: Serve your remote from your machine.
5. Install the module into your sandbox
Run the command create printed, in a terminal where TV_API_TOKEN still holds your portal key from step 2 (or where you ran login):
npx @tv/extension-sdk sandbox install module-manifest.json --sandbox=sbx_... --remote-entry=http://localhost:6001/assets/remoteEntry.js--sandbox looks the sandbox up on the service that manages sandboxes, with your portal key, and then uses the sandbox's own token for the install itself. You never handle that token. The command is one line on purpose: a \ continuation works in bash, but pasted into PowerShell or cmd it becomes two commands.
sbx_… is your sandbox id from step 3, not the slug in its URL. Lost it? npx @tv/extension-sdk sandbox list shows the id of every sandbox you own; the portal lists sandboxes by name only.
This registers the manifest in your sandbox's registry with the localhost remote entry, and licenses the module on the seeded building. It prints the URL to open. Re-run it whenever you change the manifest; code changes need no re-install, because the shell loads your remote fresh.
Pass the remote entry exactly as above. Not 127.0.0.1 (see step 4), and not the pnpm dev port 5001: the dev server answers that path with an HTML page, so the shell shows Module failed to load and the browser console reports a module script with the MIME type "text/html".
If the command fails, its message names the step and the service that refused: the lookup on the service that manages sandboxes, or registering the manifest, reading buildings or granting the licence on your sandbox's tv-api. The fixes are in When something is wrong.
In depth: Install the module into your sandbox.
6. Open it in the sandbox shell
https://<slug>.sandbox.k8s.tangovision.dev/sandbox/hello- Use Chrome or Firefox. Invited developers sign in with the sandbox login we sent; the first sign-in asks you to set a new password. Employees click Tango Vision staff and use their company account.
- You land in Building OS as Administrator with your seeded building selected.
- Open
/sandbox/hello. Chrome may ask once for permission to reach your local network; allow it. YourShell.tsxrenders inside the shell.
Edit, save, reload. dev:remote rebuilds, and the shell picks up the new bundle on the next load.
In depth: Open it and Sign in.
Two developers, one sandbox
A sandbox belongs to the organisation of the developer account that created it, and each developer account is an organisation of its own. So when two of you work on one sandbox, the one who did not create it cannot reach it with a key of their own: sandbox list shows nothing, and sandbox install --sandbox=… answers 404. To share one:
Developer A creates the sandbox (step 3) and sends us its slug as usual, saying who else works on it. We create a sandbox login for each of you.
We send developer B an API key that acts for developer A's organisation. It expires on the date we give you. B uses it in place of a portal key:
bashexport TV_API_TOKEN=tvk_... # the key we sent developer B npx @tv/extension-sdk sandbox list # now shows the shared sandboxKeys that B mints in the portal still belong to B's own organisation and do not see the shared sandbox, and B's portal page lists no sandboxes.
Each of you serves the module from your own machine with steps 4 and 6, on
http://localhost:6001. The registry row points atlocalhost, which each browser resolves to its own machine, so each of you sees your own build.The sandbox holds one registry row per module. Whoever runs
sandbox installlast overwrites it for both of you, so re-install only when the manifest changes, always with--remote-entry=http://localhost:6001/assets/remoteEntry.js, and agree which version of the manifest is current.Both keys can extend, reset and delete the shared sandbox, and a sandbox created with either one counts against the same limit of 3. Agree between you who does those.
When the sandbox is replaced by a new one, send us the new slug, and we point both logins at it.
Rules that explain the blank pages
- Only a sandbox shell loads a module from localhost. The production shell never will; your module reaches customers only after we review the source and build it in our CI.
- Loopback only:
localhostor127.0.0.1. A LAN address or a tunnel is refused. Registerlocalhost, because on macOSserve:remotedoes not answer on127.0.0.1. - The module renders in your browser only. A colleague opening the URL gets a load error, because localhost is their machine (unless they serve it themselves; see Two developers, one sandbox).
- Your backend runs on your machine and the browser can call it, but the platform cannot: the sandbox has no outbound internet, so Copilot cannot reach an MCP endpoint on localhost, and the sandbox Copilot cannot call an external LLM.
- Share
i18nextandreact-i18nextinvite.config.tsboth or neither, and keep theuse-sync-external-store/shimalias the scaffold wrote. Breaking either fails only inside a shell, where your CI cannot see it.
The full checklist is The rules for loading a module from localhost.
When something is wrong
| You see | Cause | Fix |
|---|---|---|
"useQuery" is not exported on build | SDK 1.14.0 scaffold | Scaffold again with 1.15.1 or later, or pnpm add -w @tanstack/react-query@^5 |
Missing required argument: <id>, or 404 from sandbox connect | No sandbox id, or the wrong one | npx @tv/extension-sdk sandbox list shows your ids (sbx_…) |
sandbox list or create fails after you exported the TV_API_URL / TV_API_TOKEN pair sandbox connect prints | The pair points the CLI at your sandbox's tv-api, not at the service that manages sandboxes (install --sandbox ignores it since 1.15.1) | unset TV_API_URL TV_API_TOKEN (or open a new terminal), then export your portal key in TV_API_TOKEN again |
⚠ TV_API_TOKEN holds a sandbox's own token (tvs_sbx_…, …) or ⚠ TV_API_TOKEN is not a developer key from the portal (tvk_…) from install | TV_API_TOKEN holds something other than your portal key, so the lookup skipped it and fell back to your login session | Export your portal key in TV_API_TOKEN again, or run npx @tv/extension-sdk login |
login fails: longer than the platform limit of 2560 chars | Windows Credential Manager cannot hold the session, and SDK 1.15.0 and earlier do not fall back | Use SDK 1.15.1 or later, which stores the session in a file instead, or the portal key in TV_API_TOKEN |
The provisioner at … rejected your developer key (TV_API_TOKEN) (401) while looking up sandbox sbx_… | Your portal key is wrong, revoked or expired | Mint a new key in the portal and put it in TV_API_TOKEN, or run npx @tv/extension-sdk login |
Cannot reach https://<slug>.sandbox.k8s.tangovision.dev/…: fetch failed right after create | The sandbox's certificate is not issued yet | Wait a minute or two and run the command again |
404 from install --sandbox, or an empty sandbox list, for a sandbox a colleague created | It belongs to your colleague's organisation | See Two developers, one sandbox |
| Sign-in says invalid redirect | Host not registered yet | Send us the slug (step 3) and wait for confirmation |
| Forgot your sandbox password | The sandbox sign-in page has no reset link | Write to your contact; we set a new temporary password |
| Employee sign-in refused right after the staff button | Not in /sandbox-developers, or the login is not linked yet | Ask to be added to the group, and ask us to link your sandbox login |
Signed in, the sidebar says Module registry unreachable — showing cached list, and /sandbox/hello says Module not in the registry after a successful install | Your sandbox login has no tenant_id yet | Tell your contact; we set it to your sandbox's organizationId |
| Module not in the registry | Not installed, or the URL slug does not match | Re-run sandbox install and reload; the URL slug is the part of the manifest id after module- |
| Module failed to load | Nothing answers at the registered URL, or it is the dev server or 127.0.0.1 | Both dev:remote and serve:remote running? curl http://localhost:6001/assets/remoteEntry.js must return JavaScript; re-install with exactly that URL |
| Blocked script in Safari | HTTPS page loading HTTP loopback | Use Chrome or Firefox |
| Every API call 401 inside the module | Reading localStorage['access_token'] | Call getAccessToken() from @tv/extension-sdk/context per request; the sandbox shell does not write that key |
| Sandbox gone | Expired: the TTL it was created with, plus any extensions | Create again with --ttl-days=30, and send us the new slug |
More: When the page is blank.
What you can and cannot do
- You can create, reset, extend and delete your own sandboxes and install modules into them. Your sandbox token administers that sandbox only.
- You cannot reach another sandbox's database or any production tenant. NetworkPolicies stand between the sandbox namespace and production, and your sandbox's database role cannot open another sandbox's database or the server's
postgresdatabase. Sign-in is not separated yet: as of 2026-09-26, every sandbox accepts a valid login from the shared sandbox realm, whichever sandbox the login was issued for. See What is isolated, and what is shared. - You cannot publish to the platform registry or to customers. That path does not exist yet; promotion is a manual review by Tango Vision.
- Sandboxes are shared infrastructure with a fixed quota. Treat them as disposable, use them for development only, and keep only synthetic data in them.
The developer platform is in preview, and sandboxes come without uptime guarantees. Questions: the contact named in your welcome message, or developers@tango.vision.