Skip to content

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.

  1. Install the SDK and its companions.

    The SDK is the only required dependency. @platform/dev-harness gives you the standalone stand-in you run behind, and @platform/design-system registers the shared <ds-*> components.

    Terminal window
    npm install @platform/sdk @platform/dev-harness @platform/design-system
  2. 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.changed so 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"],
    };
  3. 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");
  4. Read the session in your root component.

    Inject the token, read platform.session for the initial snapshot, subscribe to session.changed for later updates, and call platform.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();
    }
    }
  5. Wire bootstrap in main.ts.

    bootstrap owns the handshake. The fallback is 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));
  6. Run it.

    Terminal window
    ng serve

    Open 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.

  • bootstrap detected there was no shell, caught the connect failure, and used your fallback.
  • 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, connect succeeds, fallback is never called, and platform.session carries a real user — no code changes.