Developer Docs
Widget SDK
Early access · v1
What is the Widget SDK?
The WidgetFlow Widget SDK lets you build custom widgets that run directly on the canvas alongside the built-in ones. Each widget is a React component — or any JavaScript — hosted inside a sandboxed iframe. Your code never touches the WidgetFlow codebase; it communicates with the host through a secure postMessage contract.
The SDK handles initialization, state persistence, theme syncing, and AI access. You write a component. WidgetFlow handles everything else.
Entry point
Import one hook. It returns everything your widget needs.
import { useWidgetFlow } from "@widgetflow/sdk";
export default function MyWidget() {
const { state, saveState, theme, user, sdk } = useWidgetFlow();
// ...
}Props contract
state / saveState
stateis your widget's persisted JSON blob — any shape you want. null on first load. Call saveState(newState) to persist changes. Rate-limited to one call per second. State must be JSON-serializable.
theme: ThemeVars
interface ThemeVars {
background: string; // e.g. "#0a0a0a"
surface: string; // e.g. "#171717"
border: string; // e.g. "rgba(255,255,255,0.05)"
text: string; // e.g. "#ffffff"
textMuted: string; // e.g. "#a3a3a3"
accent: string; // e.g. "#8b5cf6"
accentGradient: string; // e.g. "linear-gradient(to right, #8b5cf6, #7c3aed)"
}Apply as inline style attributes — these are raw CSS strings, not Tailwind classes.
user: UserContext
username, displayName, and avatarUrl for the WidgetFlow user currently viewing this widget.
SDK methods
Available via the sdk object returned by the hook. All methods return Promises.
// Persistent key-value storage (scoped to widget + user)
sdk.storage.get(key: string): Promise<any>
sdk.storage.set(key: string, value: any): Promise<void>
sdk.storage.delete(key: string): Promise<void>
// AI
sdk.ai.prompt(text: string, options?: { persona?: string }): Promise<string>Database, file storage, and external connections are planned for future SDK versions.
Example: Counter widget
A counter that persists across sessions and matches the host theme. This is the smallest complete example that exercises state, saveState, theme, and userContext.
"use client";
import { useWidgetFlow } from "@widgetflow/sdk";
interface CounterState { count: number; }
export default function CounterWidget() {
const { state, saveState, theme, user } = useWidgetFlow();
const count = (state as CounterState | null)?.count ?? 0;
return (
<div style={{ background: theme.background, color: theme.text,
display: "flex", flexDirection: "column",
alignItems: "center", justifyContent: "center",
height: "100%", padding: "24px" }}>
{user.username && (
<span style={{ color: theme.textMuted, fontSize: "11px",
position: "absolute", top: 12, right: 14 }}>
@{user.username}
</span>
)}
<div style={{ fontSize: "72px", fontWeight: 800,
background: theme.accentGradient,
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent" }}>
{count}
</div>
<div style={{ display: "flex", gap: "12px" }}>
<button onClick={() => saveState({ count: count - 1 })}>−</button>
<button onClick={() => saveState({ count: count + 1 })}>+</button>
</div>
</div>
);
}Constraints
- —One file, one component. Your widget is a single file with a default-exported React component.
- —No external network requests. The iframe Content Security Policy blocks outbound requests. Use
sdk.ai.promptfor AI andsdk.storagefor data. - —No localStorage or sessionStorage. The sandbox blocks direct storage access. All persistence goes through
saveStateandsdk.storage. - —State must be JSON-serializable. No functions, class instances, or circular references.
- —Inline styles for theming. Tailwind CSS is not available inside the iframe. Use the
themevalues as inlinestyleprops.
Apply for early access
The Widget SDK is in private early access. Reach out with what you want to build and we'll get you set up.
Request access →