WAM is the frontend runtime for Channel app UI. In practice, WAM is how command actions, widgets, and custom tabs render interactive experiences.
A WAM is not an app server and does not own server credentials. The Channel host injects runtime data and exposes a bridge for app/native Function calls.
npm install @channel.io/app-sdk-wamUse WAM when a server-side extension action should open UI instead of returning plain text.
Common entry points:
- command action returns
{ type: "wam", attributes: ... } - widget action returns
{ type: "wam", attributes: ... } - custom tab action returns
{ type: "wam", attributes: ... }
For example:
{
"type": "wam",
"attributes": {
"appId": "public-app-id",
"name": "tutorial",
"wamArgs": { "view": "summary" }
}
}Register the WAM Endpoint root and serve the built SPA from ${WAM_ENDPOINT}/${name}. wamArgs and injected runtime data are client-readable; never use them to transport a secret or token.
import React from "react";
import ReactDOM from "react-dom/client";
import { WamProvider } from "@channel.io/app-sdk-wam";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<WamProvider>
<App />
</WamProvider>
</React.StrictMode>,
);Read values injected by the Channel runtime. The most common keys are:
appIdchannelIdmanagerIdchatIdchatTyperootMessageId- custom keys passed in
wamArgs
Call your own app function. This is the main way a WAM UI talks back to your server extension logic. Use it for business logic and for work that must execute as the app or bot. The app server can obtain a channel token without exposing it to the WAM.
const appId = useWamData("appId") as string;
const { call, loading, error } = useCallFunction({
appId,
name: "calendar.booking.createBooking",
});Call a Channel native function that is exposed to the current role and surface. This is useful for manager-scoped OAuth or API key management flows, and for other runtime-native integrations. Authorization comes from the current Channel surface and manager/user role. The WAM does not receive or mint that token.
const { call } = useNativeFunction({
name: "getOAuthAuthorizationURL",
});Resize the current WAM window or panel. Use it on mount and when content size changes.
Close the current WAM surface.
The usual WAM loop looks like this:
- Read runtime context from
useWamData() - Call app functions with
useCallFunction() - Call native functions only when the flow really needs Channel-owned capabilities
- Resize with
useWamSize()when content changes
- Command
- Widget
- Custom tab
- Some OAuth or API key setup flows
Calendar is a good concrete example because it combines:
- server-side extension functions
- a WAM booking UI
- command/widget/custom-tab style action results if you choose to wrap it that way
- Prefer
useCallFunction()for your own business logic - Treat
useNativeFunction()as the boundary to Channel-owned capabilities - Pass only the minimum needed
wamArgs - Never put App Secret, Signing Key, app/channel token, provider token, or config credentials in WAM code or data
- Validate authorization again in server Functions; client UI visibility is not an authorization boundary
- Set an initial size early
- Keep action handlers small and push real logic into app functions
For the complete credential model, read Authentication and Tokens. For a runnable implementation, see the TypeScript tutorial.