Getting started
This guide takes you from zero to a registered Tango Vision module.
What you need
- Node 22+ and npm
- Access to the private registry
https://npm.k8s.tangovision.dev/ - A sandbox API key (ask the platform team, or see On-demand sandboxes)
Install the SDK
bash
npm install @tv/extension-sdk.npmrc:
@tv:registry=https://npm.k8s.tangovision.dev/The SDK ships everything in one package, exposed via subpaths:
| Subpath | What it gives you |
|---|---|
@tv/extension-sdk | ModuleManifest types + Zod validator + PlatformContext types |
@tv/extension-sdk/manifest | Manifest schema, validator, JSON Schema |
@tv/extension-sdk/context | Runtime context types |
@tv/extension-sdk/react | <PlatformProvider>, usePlatformContext(), useBuilding(), useCurrentUser() |
@tv/extension-sdk/nestjs | @ModuleCapability(), @RequiresLicense() decorators |
@tv/extension-sdk/testing | createMockPlatformContext() |
tv-sdk (bin) | CLI validator |
The anatomy of a module
my-module/
├── module-manifest.json ← the contract
├── frontend/ ← React, exposes a federated "Shell"
│ └── src/App.tsx
└── backend/ ← optional NestJS service
└── src/The manifest is the heart of it. It declares your module's id, the permissions it needs, the events it speaks, and where its UI mounts. The platform reads it three times:
- In your CI —
tv-sdk validate module-manifest.json - At publish — the registry rejects an invalid manifest
- At runtime — the Building OS shell composes your module from it
The golden rule
Your module talks to the platform only through
PlatformContext.
No localStorage. No manual tokens. No hand-built API URLs. The context gives you a pre-authenticated, tenant-scoped API client. This is what lets the same code run in your sandbox and in a customer's production tenant unchanged.
tsx
import { usePlatformContext, useBuilding } from '@tv/extension-sdk/react';
export function WorkOrderList() {
const { api } = usePlatformContext(); // already authenticated + scoped
const building = useBuilding(); // the active building
return useQuery({
queryKey: ['work-orders', building.id],
queryFn: () => api.get(`/api/v1/buildings/${building.id}/work-orders`),
});
}Next
→ Your first module builds a working hello-world end to end.