From zero to your module in your sandbox
From an empty folder to your module rendering inside Building OS, bound to a sandbox that is yours alone. About 30 minutes the first time. 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 developers@tango.vision before going further.
- 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. 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 and pnpm 10.18.2, exactly. Chrome or Firefox for the last step: Safari cannot load a module from your machine.
One-time setup
# ~/.npmrc — the token comes from your registration email
@tv:registry=https://npm.k8s.tangovision.dev/
//npm.k8s.tangovision.dev/:_authToken=${TV_NPM_TOKEN}export TV_NPM_TOKEN=... # keep it in your shell profile or a secret manager
npm view @tv/extension-sdk version # expect 1.15.0 or laterUse @tv/extension-sdk 1.15.0. 1.14.1 is the oldest version whose scaffold builds: 1.14.0 scaffolds a project that does not build. 1.14.1 and earlier also name the federation container tv-module-<slug> instead of the camelCase slug the platform expects; if you scaffolded with one of them, set name in vite.config.ts to the camelCase slug (hello for hello, workOrders for work-orders). Details, including the Docker build-secret pattern: Getting started.
1. Scaffold the module
npx @tv/extension-sdk init-module hello --category=operations --external
cd tv-module-hello
pnpm install--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 loginOn Windows, use the key. login in SDK 1.15.0 and earlier cannot save the session there: it is larger than Windows Credential Manager holds. If you need login, set TV_SDK_TOKEN_STORE=file first, and keep it set for later commands.
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=5000Provisioning takes one to a few minutes; the command polls until the sandbox is ready. You get:
| Field | Value |
|---|---|
id | sbx_… — your sandbox id, which step 5 takes |
apiUrl | https://<slug>.sandbox.k8s.tangovision.dev |
consoleUrl | The same host: the sandbox shell |
organizationId | Your sandbox's tenant id, unique to this sandbox |
apiToken | Administrator of this sandbox only |
Inside: a seeded office building with storeys, spaces and equipment, its own PostgreSQL database and its own tv-api. It expires after 14 days by default (extend up to twice, 7 days each). The data is synthetic, and nothing you store survives expiry. Types: office, mall, university. Up to 3 sandboxes at a time.
Now send us the slug and organizationId
We register the sandbox host for sign-in and create (or, for employees, link) your sandbox shell login with tenant_id set to your organizationId. Until we do, the shell cannot sign you in. Every time you recreate a sandbox, this step repeats.
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:
pnpm dev:remote # vite build --watch
pnpm serve:remote # vite preview --port 6001 --strictPort, CORS onYour remote is at http://localhost:6001/assets/remoteEntry.js. The port is fixed on purpose: if 6001 is busy the command fails instead of silently moving, which would leave the registry pointing at nothing.
In depth: Serve your remote from your machine.
5. Install the module into your sandbox
npx @tv/extension-sdk sandbox connect sbx_... # prints export TV_API_URL=... TV_API_TOKEN=...
npx @tv/extension-sdk sandbox install module-manifest.json \
--sandbox=sbx_... \
--remote-entry=http://localhost:6001/assets/remoteEntry.jssbx_… 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.
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.
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. - The module renders in your browser only. A colleague opening the URL gets a load error, because localhost is their machine.
- 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 | Use 1.15.0, 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 sandbox connect | The TV_API_URL / TV_API_TOKEN it printed now point the CLI at your sandbox, not at the service that manages sandboxes | Use a new terminal, or add --api=https://sandbox-api.k8s.tangovision.dev |
login fails: longer than the platform limit of 2560 chars | Windows Credential Manager cannot hold the session (SDK 1.15.0 and earlier) | Use the portal key in TV_API_TOKEN, or set TV_SDK_TOKEN_STORE=file and log in again |
401 from sandbox install | Wrong token | Use the sandbox's apiToken (from sandbox connect), not the portal key |
| Sign-in says invalid redirect | Host not registered yet | Send us the slug (step 3) and wait for confirmation |
| 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, shell says no tenant | tenant_id not set on your login | Tell us; we set it to your organizationId |
| Module not found, or 404 | Not installed, or slug mismatch | Re-run sandbox install; the URL slug is the part of the manifest id after module- |
| Blank module, console shows CORS or 404 | Remote not serving | Both terminals running? curl http://localhost:6001/assets/remoteEntry.js |
| 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 (14 days by default) | Create again, 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 any other sandbox 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. 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, and use them for development only.
The developer platform is in preview, and sandboxes come without uptime guarantees. Questions: developers@tango.vision.