Quickstart
By the end of this tutorial you will have an Angular app that connects to the platform, reads the session, and renders — running standalone, with no shell in front of it. That last part matters: the standalone path is how you get to first success before you have a platform host to load you.
Everything here is real, compile-verified code. You write four small files.
-
Install the SDK and its companions.
The SDK is the only required dependency.
@platform/dev-harnessgives you the standalone stand-in you run behind, and@platform/design-systemregisters the shared<ds-*>components.Terminal window npm install @platform/sdk @platform/dev-harness @platform/design-system -
Declare a contract.
The contract is your app’s handshake declaration — what it provides, consumes, subscribes to, and the capabilities it intends to use. A first app can start almost empty; you only need to subscribe to
session.changedso the UI follows sign-in and sign-out.src/contract.ts import type { AppContract } from "@platform/sdk";// The contract is the app's handshake declaration: what it provides, consumes, subscribes to, and the// capabilities it intends to use. It is advisory — the shell sources capabilities from it, but each// backend remains the sole authorization gate. A first app can start almost empty.export const contract: AppContract = {provides: [],consumes: [],subscribes: ["session.changed", "theme.changed"],permissions: ["notify.toast"],}; -
Create an injection token for the client.
The SDK never imports Angular. You connect once, then hand the client to Angular DI through a token. This is cleaner than stashing it on
window, and it is the pattern the rest of the docs assume.src/shared/platform.token.ts import { InjectionToken } from "@angular/core";import type { Platform } from "@platform/sdk";/** The connected platform client, established in main.ts before Angular bootstraps. */export const PLATFORM = new InjectionToken<Platform>("platform.client"); -
Read the session in your root component.
Inject the token, read
platform.sessionfor the initial snapshot, subscribe tosession.changedfor later updates, and callplatform.ready()once your handlers are wired — that releases the shell’s traffic barrier.src/app/app.ts import { Component, CUSTOM_ELEMENTS_SCHEMA, inject, signal } from "@angular/core";import { SDK_VERSION, type Session } from "@platform/sdk";import { PLATFORM } from "../shared/platform.token";@Component({selector: "my-app-root",// Allows the design system's <ds-*> web components in the template.schemas: [CUSTOM_ELEMENTS_SCHEMA],template: `<main><h1>My app</h1><p>Running against SDK {{ sdkVersion }}.</p>@if (session(); as s) {<p>{{ s.authenticated ? "Signed in as " + (s.name ?? s.email) : "Not signed in" }}</p>}</main>`,})export class App {protected readonly sdkVersion = SDK_VERSION;protected readonly session = signal<Session | null>(null);private readonly platform = inject(PLATFORM);constructor() {// The session is a snapshot handed over at connect time…this.session.set(this.platform.session);// …and it can change mid-session (sign-in/out, role grants), so follow it.this.platform.subscribe<Session>("session.changed", (s) => this.session.set(s));// Release the shell's traffic barrier once handlers are wired.this.platform.ready();}} -
Wire
bootstrapinmain.ts.bootstrapowns the handshake. Thefallbackis the whole trick: when there is no shell to connect to, it hands your app a standalone stand-in instead of failing — so the app comes up on its own.src/main.ts import { type Type } from "@angular/core";import { bootstrapApplication } from "@angular/platform-browser";import { bootstrap } from "@platform/sdk";import { defineComponents } from "@platform/design-system";import { standalonePlatform } from "@platform/dev-harness";import { PLATFORM } from "../shared/platform.token";import { contract } from "./contract";// Register the design system's <ds-*> web components once, before anything renders.defineComponents();// bootstrap() owns the two-mode handshake (top-level app vs embedded surface). For a first app there// are no surfaces, so only mountApp is supplied. The fallback runs when there is no shell to connect// to, which is how the app comes up standalone during local development.void bootstrap<Type<unknown>, never>({contract,app: () => import("./app").then((m) => m.App),fallback: () => standalonePlatform({ appId: "my-app" }),mountApp: (app, platform) =>bootstrapApplication(app, {providers: [{ provide: PLATFORM, useValue: platform }],}),}).catch((err) => console.error(err)); -
Run it.
Terminal window ng serveOpen the app. It renders, shows the SDK version, and reports “Not signed in” — the honest logged-out state, because standalone has no session. You have a working platform app before you have a platform.
What just happened
Section titled “What just happened”bootstrapdetected there was no shell, caught theconnectfailure, and used yourfallback.- The standalone client is genuinely sessionless: no fake user, no mock data. Your app falls through to its real logged-out state, which is exactly what you want to build against.
- When this same app is later loaded inside the shell,
connectsucceeds,fallbackis never called, andplatform.sessioncarries a real user — no code changes.