JavaScript SDK
The @yak-io/javascript package provides the core client SDK and server handlers. It works with any JavaScript runtime that supports the Fetch API.
When to Use
- Building with frameworks without a dedicated SDK (Solid, Ember, etc.)
- Need server handlers for custom runtimes (Cloudflare Workers, Deno, Bun)
- Building a custom integration without framework components
Installation
npm install @yak-io/javascriptpnpm add @yak-io/javascriptyarn add @yak-io/javascriptbun add @yak-io/javascriptShips both ESM and CommonJS, so it works with native ESM, bundlers, and CommonJS
require() / Jest out of the box — no transform config needed. See
Module Format Issues if you
hit an Unexpected token 'export' error on older tooling.
Server Handlers
Create API endpoints that serve route configuration and handle tool calls:
import { createYakHandler } from "@yak-io/javascript/server";
const { GET, POST } = createYakHandler({
routes: [
{ path: "/", title: "Home" },
{ path: "/pricing", title: "Pricing" },
],
tools: [
{
id: "custom",
getTools: async () => [
{
name: "greet",
description: "Greet a user by name",
inputSchema: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
},
},
],
executeTool: async (name, args) => {
if (name === "greet") {
return { message: `Hello, ${args.name}!` };
}
throw new Error(`Unknown tool: ${name}`);
},
},
],
});
export { GET, POST };Route Schema
| Property | Type | Required | Description |
|---|---|---|---|
path | string | Yes | URL path |
title | string | No | Human-readable title |
description | string | No | Brief description for AI context |
Dynamic Route Sources
Fetch routes from external sources:
const cmsRoutes = {
id: "cms",
getRoutes: async () => {
const res = await fetch("https://cms.example.com/api/pages");
return res.json();
},
};
const { GET, POST } = createYakHandler({
routes: [staticRoutes, cmsRoutes],
});Drop-in widget (YakEmbed)
For vanilla JS apps, YakEmbed renders the trigger pill and manages the chat iframe and voice session for you:
import { YakEmbed } from "@yak-io/javascript";
const embed = new YakEmbed({
appId: "your-app-id",
mode: "both", // "chat" (default) | "voice" | "both"
getConfig: async () => fetch("/api/yak").then((r) => r.json()),
onToolCall: async (name, args) => {
const res = await fetch("/api/yak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, args }),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error);
return data.result;
},
});
embed.mount();The same onToolCall and getConfig handlers serve chat and voice — see Voice Mode for the full voice API exposed via embed.voiceStart() / voiceStop() / voiceToggle().
Client SDK
The YakClient class manages widget communication. For most use cases, use @yak-io/react or @yak-io/nextjs instead — this is the low-level API.
import { YakClient } from "@yak-io/javascript";
const client = new YakClient({
appId: "your-app-id",
onToolCall: async (name, args) => {
const res = await fetch("/api/yak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, args }),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error);
return data.result;
},
onReady: () => console.log("Widget ready"),
onClose: () => console.log("Widget closed"),
});
client.mount();
client.setWidgetOpen(true);
client.sendPrompt("Help me");
client.unmount();Configuration Options
| Option | Type | Description |
|---|---|---|
appId | string | Your Yak application ID |
onToolCall | (name, args) => Promise<unknown> | Tool execution handler |
onToolCallComplete | (event: ToolCallEvent) => void | Called after each tool call completes |
onRedirect | (path: string) => void | Navigation handler |
onReady | () => void | Called when the widget is ready |
onClose | () => void | Called when the widget is closed |
theme | Theme | Widget styling options |
options.disablePageContent | boolean | Stop sending any page context (URL, title, and visible text) to the assistant. The widget still works, but it won't be aware of the page the user is on. |
user | { id, hash } | Signed end-user identity. Enables conversation persistence and history. |
Privacy: By default Yak shares the current page's URL, title, and visible text with the assistant so it can answer questions about the page the user is viewing. Set options.disablePageContent to turn this off entirely — the SDK then sends nothing about the page (not even the URL), so the assistant can't answer page-specific questions.
Runtime Examples
Cloudflare Workers
import { createYakHandler } from "@yak-io/javascript/server";
const { GET, POST } = createYakHandler({
routes: [{ path: "/", title: "Home" }],
});
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/yak") {
if (request.method === "GET") return GET(request);
if (request.method === "POST") return POST(request);
}
return new Response("Not Found", { status: 404 });
},
};Hono
import { Hono } from "hono";
import { createYakHandler } from "@yak-io/javascript/server";
const app = new Hono();
const { GET, POST } = createYakHandler({
routes: [{ path: "/", title: "Home" }],
});
app.get("/api/yak", (c) => GET(c.req.raw));
app.post("/api/yak", (c) => POST(c.req.raw));
export default app;Deno
import { createYakHandler } from "@yak-io/javascript/server";
const { GET, POST } = createYakHandler({
routes: [{ path: "/", title: "Home" }],
});
Deno.serve(async (request) => {
const url = new URL(request.url);
if (url.pathname === "/api/yak") {
if (request.method === "GET") return GET(request);
if (request.method === "POST") return POST(request);
}
return new Response("Not Found", { status: 404 });
});Bun
import { createYakHandler } from "@yak-io/javascript/server";
const { GET, POST } = createYakHandler({
routes: [{ path: "/", title: "Home" }],
});
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/api/yak") {
if (request.method === "GET") return GET(request);
if (request.method === "POST") return POST(request);
}
return new Response("Not Found", { status: 404 });
},
});Express / Node.js
import express from "express";
import { createYakHandler } from "@yak-io/javascript/server";
const app = express();
app.use(express.json());
const { GET, POST } = createYakHandler({
routes: [{ path: "/", title: "Home" }],
});
app.all("/api/yak", async (req, res) => {
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
const request = new Request(url, {
method: req.method,
headers: req.headers as HeadersInit,
body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
});
const response = await (req.method === "POST" ? POST(request) : GET(request));
res.status(response.status);
response.headers.forEach((value, key) => res.setHeader(key, value));
res.send(Buffer.from(await response.arrayBuffer()));
});
app.listen(3000);TypeScript
Import types for your integrations:
// Client-side types
import type {
YakClientConfig,
Theme,
ThemeColors,
ToolCallHandler,
ToolCallEvent,
} from "@yak-io/javascript";
// Server-side types
import type {
RouteInfo,
RouteSource,
ToolDefinition,
ToolSource,
ToolExecutor,
ToolCallPayload,
ToolCallResult,
} from "@yak-io/javascript/server";