React bindings for Pillar, the open-source product copilot SDK. Add an AI assistant to your React or Next.js app that executes tasks using your UI. GitHub · Docs
Pillar is a product copilot for SaaS and web apps. Users say what they want, and Pillar uses your UI to make it happen.
A CRM user could ask:
"Close the Walmart deal as won and notify implementation"
An analytics user could ask:
"Add a weekly signups chart to my dashboard"
Pillar understands the intent, builds a plan, and executes it client-side with the user's session. No proxy servers, no token forwarding.
Full docs · React quickstart · API reference
npm install @pillar-ai/react
# or
pnpm add @pillar-ai/react
# or
yarn add @pillar-ai/reactSign up at app.trypillar.com and grab your agent slug from the dashboard.
Wrap your app with PillarProvider:
import { PillarProvider } from "@pillar-ai/react";
function App() {
return (
<PillarProvider agentSlug="your-agent-slug">
<YourApp />
</PillarProvider>
);
}For Next.js App Router, create a client wrapper:
// providers/PillarSDKProvider.tsx
"use client";
import { PillarProvider } from "@pillar-ai/react";
export function PillarSDKProvider({ children }: { children: React.ReactNode }) {
return (
<PillarProvider agentSlug="your-agent-slug">{children}</PillarProvider>
);
}// app/layout.tsx
import { PillarSDKProvider } from "@/providers/PillarSDKProvider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<PillarSDKProvider>{children}</PillarSDKProvider>
</body>
</html>
);
}Tools tell Pillar what your app can do. Define them with the usePillarTool hook:
import { usePillarTool } from "@pillar-ai/react";
import { useRouter } from "next/navigation";
export function usePillarTools() {
const router = useRouter();
usePillarTool({
name: "open_settings",
type: "navigate",
description: "Navigate to the settings page",
examples: ["open settings", "go to settings"],
autoRun: true,
execute: () => router.push("/settings"),
});
}Call usePillarTools() from any component inside PillarProvider (e.g. your layout).
Tools are the building blocks. When a user makes a request, Pillar matches intent to tools and executes them.
usePillarTool({
name: "open_dashboard",
type: "navigate",
description: "Navigate to the main dashboard",
examples: ["go to dashboard", "show me the dashboard"],
execute: () => router.push("/dashboard"),
});Pillar extracts parameters from the user's request:
usePillarTool({
name: "view_user_profile",
type: "navigate",
description: "View a specific user's profile page",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "The user ID to view" },
},
required: ["userId"],
},
execute: ({ userId }) => router.push(`/users/${userId}`),
});usePillarTool([
{
name: "open_billing",
type: "navigate",
description: "Navigate to billing and subscription settings",
examples: ["go to billing", "view my subscription"],
execute: () => router.push("/settings/billing"),
},
{
name: "open_team",
type: "navigate",
description: "Navigate to team management page",
examples: ["manage team", "invite team members"],
execute: () => router.push("/settings/team"),
},
]);| Type | Use case |
|---|---|
navigate |
Navigate to a page or view |
trigger_tool |
Run a function (export, toggle, API call) |
query |
Return data to the assistant |
inline_ui |
Render a custom component in the chat |
external_link |
Open an external URL |
copy_text |
Copy text to clipboard |
See Setting Up Tools for the full guide.
Access SDK state and methods:
import { usePillar } from "@pillar-ai/react";
function MyComponent() {
const {
pillar, // SDK instance
isReady, // Whether SDK is initialized
isPanelOpen, // Panel open state
open, // Open the panel
close, // Close the panel
toggle, // Toggle the panel
navigate, // Navigate to a view
setTheme, // Update theme at runtime
on, // Subscribe to events
} = usePillar();
if (!isReady) return <div>Loading...</div>;
return <button onClick={() => open()}>Get Help</button>;
}Panel-specific controls:
import { useHelpPanel } from "@pillar-ai/react";
function HelpButton() {
const {
isOpen,
open,
close,
toggle,
openArticle,
openCategory,
openSearch,
openChat,
} = useHelpPanel();
return (
<div>
<button onClick={toggle}>{isOpen ? "Close" : "Help"}</button>
<button onClick={openChat}>Ask co-pilot</button>
</div>
);
}| Method | Description |
|---|---|
open(options?) |
Open the panel |
close() |
Close the panel |
toggle() |
Toggle open/closed |
openArticle(slug) |
Open a specific article |
openCategory(slug) |
Open a category view |
openSearch(query?) |
Open search with optional query |
openChat() |
Open the co-pilot chat view |
The root component. Initializes and configures the SDK.
| Prop | Type | Required | Description |
|---|---|---|---|
agentSlug |
string |
Yes | Your agent slug from app.trypillar.com |
config |
object |
No | SDK configuration (panel, theme, triggers) |
onTask |
(task) => void |
No | Generic handler for dynamic tools |
<PillarProvider
agentSlug="your-agent-slug"
config={{
// userApiToken: accessToken, // forward auth to API tool sources
panel: { position: "right", mode: "push", width: 400 },
theme: { mode: "auto", colors: { primary: "#2563eb" } },
}}
>
{children}
</PillarProvider>For custom panel placement, set panel.container to 'manual' and render PillarPanel where you want it:
import { PillarProvider, PillarPanel } from "@pillar-ai/react";
function App() {
return (
<PillarProvider
agentSlug="your-agent-slug"
config={{ panel: { container: "manual" } }}
>
<div className="layout">
<main>Your content</main>
<PillarPanel className="sidebar-panel" />
</div>
</PillarProvider>
);
}For dynamic or backend-triggered tools, use the onTask prop on PillarProvider. For most cases, prefer usePillarTool which co-locates the handler with the tool definition.
<PillarProvider
agentSlug="your-agent-slug"
onTask={(task) => {
if (task.name.startsWith("nav_")) {
router.push(task.data.path);
return;
}
switch (task.name) {
case "notification":
showNotification(task.data.message);
break;
case "refresh_data":
queryClient.invalidateQueries();
break;
}
}}
>
{children}
</PillarProvider>For inline_ui tools, use the render prop to display custom React components in the chat. The AI agent provides data directly to the render component — no execute function needed:
import { usePillarTool, type ToolRenderProps } from "@pillar-ai/react";
function InviteCard({ data }: ToolRenderProps<{ emails: string[]; teamName: string }>) {
return (
<div className="p-4 border rounded">
<p>Invite {data.emails?.length || 0} members to {data.teamName}</p>
</div>
);
}
usePillarTool({
name: "invite_members",
description: "Invite team members via email",
type: "inline_ui",
inputSchema: {
type: "object",
properties: {
emails: { type: "array", items: { type: "string" } },
teamName: { type: "string" },
},
},
render: InviteCard,
});The render component receives:
data— Data provided by the AI agent (extracted from the conversation viainputSchema)sendResult(result)— Send a result back to the AI agent, continuing the conversationcontext— Card position info (isLatest,isReady,messageIndex,segmentIndex,toolName)onStateChange?(state, message?)— optional loading/success/error states
For tools that perform destructive or irreversible actions, use needsConfirmation to require user approval before execute runs:
usePillarTool({
name: "delete_project",
description: "Permanently delete a project and all its data",
type: "trigger_tool",
needsConfirmation: true,
inputSchema: {
type: "object",
properties: {
projectId: { type: "string", description: "Project ID to delete" },
},
required: ["projectId"],
},
execute: async ({ projectId }) => {
await api.deleteProject(projectId);
return { deleted: true };
},
});The SDK shows a Confirm/Cancel card. The user must click Confirm before execute runs.
For custom confirmation UI, use renderConfirmation:
import { usePillarTool, type ConfirmationRenderProps } from "@pillar-ai/react";
function ConfirmPurchase({
data,
onConfirm,
onCancel,
isReady,
isLatest,
}: ConfirmationRenderProps<{ total: number; cartId: string }>) {
return (
<div className={`p-4 border rounded ${!isLatest ? "opacity-50" : ""}`}>
<p>Complete purchase for ${data.total}?</p>
<div className="flex gap-2 mt-4">
<button onClick={() => onConfirm()} disabled={!isReady}>
Buy Now
</button>
<button onClick={onCancel}>Cancel</button>
</div>
</div>
);
}
usePillarTool({
name: "complete_purchase",
description: "Complete the purchase",
type: "trigger_tool",
renderConfirmation: ConfirmPurchase,
execute: async ({ cartId }) => {
await api.checkout(cartId);
return { success: true };
},
});Providing renderConfirmation implies needsConfirmation — you don't need to set both.
The ConfirmationRenderProps interface provides:
| Prop | Type | Description |
|---|---|---|
data |
T |
Data the AI extracted via inputSchema |
onConfirm |
(modifiedData?) => void |
Approve the action. Optionally pass modified data to override what the AI sent to execute. |
onCancel |
() => void |
Dismiss the confirmation without executing |
isReady |
boolean |
True when the AI is not streaming — safe to interact |
isLatest |
boolean |
True when this is the most recent card in the chat |
| Export | Description |
|---|---|
PillarProvider |
Context provider that initializes the SDK |
PillarPanel |
Component for custom panel placement |
usePillar |
Hook for SDK access and panel control |
useHelpPanel |
Hook for panel-specific controls |
usePillarTool |
Hook to register tools |
| Package | Description |
|---|---|
| @pillar-ai/sdk | Core vanilla JS SDK |
| @pillar-ai/vue | Vue 3 bindings |
| @pillar-ai/svelte | Svelte bindings |
- React 17+ or React 19+
- React DOM 17+ or React DOM 19+
MIT