Search documentation

Search documentation

Setting Up Tools

This guide walks through implementing tools in your application. For a conceptual overview of what tools are and why they matter, see Tools.

Build with AI

Use this prompt with Cursor or Claude Code to automatically generate tools tailored to your application:

Tip: Use.to enable Plan mode in Cursor for best results
# Build Pillar SDK Tools for My App

I want to implement Pillar SDK tools for my application. Help me create a set of tools that will make my app's co-pilot useful.

## Pillar SDK Overview

Pillar tools are defined using `usePillarTool()` in React or `pillar.defineTool()` in vanilla JS. Here's the pattern:

```tsx
// hooks/usePillarTools.ts
import { usePillarTool } from '@pillar-ai/react';

function useAppTools() {
  usePillarTool({
    name: 'tool_name',
    description: 'AI-friendly description of what this does',
    guidance: 'When and how to use this tool, pitfalls, chaining notes',
    type: 'navigate' | 'trigger_tool' | 'query' | 'inline_ui' | 'external_link' | 'copy_text',
    examples: ['phrases users might say'],
    autoRun: true,            // execute without clicking
    autoComplete: true,       // mark done immediately
    inputSchema: { ... },     // JSON Schema for data extraction
    requiredContext: { ... },  // context filtering
    execute: (data) => { ... },
  });
}
```

### Tool Types
- **navigate**: Go to a page (use with `autoRun: true`)
- **trigger_tool**: Run custom logic (modals, wizards, toggles, API calls)
- **query**: Fetch data and return it to the AI agent for reasoning
- **inline_ui**: Show interactive UI in chat
- **external_link**: Open URL in new tab
- **copy_text**: Copy to clipboard

### defineTool() Pattern (non-React)
```tsx
// tools/dashboardTools.ts
export function registerDashboardTools(pillar: PillarInstance) {
  return [
    pillar.defineTool({
      name: 'create_dashboard',
      description: 'Create a new empty dashboard',
      guidance: 'First step in dashboard workflow. Returns dashboard_uid for panel tools.',
      type: 'trigger_tool',
      autoRun: true,
      inputSchema: {
        type: 'object',
        properties: {
          title: { type: 'string', description: 'Dashboard title' },
        },
        required: ['title'],
      },
      execute: async (data) => {
        const result = await api.createDashboard(data.title);
        return { uid: result.uid };
      },
    }),
  ];
}
```

## Your Task

1. **Analyze my codebase** to understand:
   - Framework (Next.js, React, Vue, etc.)
   - Routing pattern and all navigable pages
   - Modal/dialog systems (what modals exist?)
   - Key user flows and features
   - Authentication and role patterns

2. **Identify tools** in these categories:

   **Navigation Tools** (type: 'navigate')
   - All main pages users might want to reach
   - Settings sections (account, billing, notifications, etc.)
   - Dashboard views and detail pages
   - Use `autoRun: true` for simple navigations

   **Trigger Tools** (type: 'trigger_tool')
   - Modals: invite users, create items, confirmations
   - Wizards: onboarding, setup flows, multi-step processes
   - Feature toggles: dark mode, sidebar, notifications
   - Tools that open forms or dialogs

   **Query Tools** (type: 'query')
   - Discovery tools that fetch data for the AI: list items, get details, inspect schemas
   - Validation tools: test a query, preview results before committing
   - These return data to the AI agent so it can reason and decide what to do next
   - Use `autoRun: true` and `autoComplete: true` so the agent can chain them without user clicks

   **API Tools** (type: 'trigger_tool')
   - Tools that create, update, or delete via API and return the result
   - Break up by operation: `create_X`, `update_X`, `delete_X` -- not one `manage_X`
   - Each gets a tight `inputSchema` matching only the fields that specific operation needs
   - All tools return data to the agent by default -- the agent gets confirmation and can continue
   - Add a `guidance` field with per-tool instructions (pitfalls, chaining notes, etc.)
   - Use `outputSchema` with `sensitive: true` for fields that should not be logged (API keys, secrets)

   **Context-Filtered Tools**
   - Admin-only tools with `requiredContext: { userRole: 'admin' }`
   - Feature-gated tools based on subscription/plan
   - Page-specific tools

3. **Generate implementation files:**

   a. `hooks/usePillarTools.ts`:
      - 10-30 tools depending on app complexity
      - Include query/discovery tools, not just navigation and triggers
      - Clear, specific descriptions that help AI matching
      - `guidance` field for tools that need usage instructions
      - Example phrases for each tool (3-5 per tool)
      - inputSchema for tools that need extracted data

4. **For each tool, include:**
   - A specific description (not generic)
   - A `guidance` field when the tool has usage caveats, chaining requirements, or pitfalls
   - Multiple example phrases users might say
   - The right tool type
   - Any required context filtering

5. **Design for verification** (for apps with create/update APIs):

   For tools that modify data, consider adding a companion query tool that lets the AI validate first. This prevents the agent from committing broken configurations.

   Pattern:
   1. `test_*` or `preview_*` tool with `type: 'query'` -- returns data for the agent to inspect
   2. `create_*` tool with `type: 'trigger_tool'` -- commits the change and returns confirmation
   3. Agent guidance tells the AI to always test before creating

   Example: `test_report_query` -> verify data exists -> `create_report`

   ```tsx
   usePillarTool({
     name: 'test_report_query',
     description: 'Test a report query and return sample results',
     type: 'query',
     guidance: 'Use this before create_report to verify the query returns expected data.',
     inputSchema: {
       type: 'object',
       properties: {
         datasource_id: { type: 'number', description: 'Dataset to query' },
         filters: { type: 'object', properties: {} },
       },
       required: ['datasource_id'],
     },
     execute: async (data) => {
       const results = await api.testQuery(data);
       return { valid: results.length > 0, row_count: results.length, sample: results.slice(0, 3) };
     },
   });

   usePillarTool({
     name: 'create_report',
     description: 'Create a report from a validated query',
     type: 'trigger_tool',
     guidance: 'Always test the query with test_report_query first. Only create if valid.',
     inputSchema: { ... },
     execute: async (data) => { ... },
   });
   ```

## Start Here

First, explore my codebase:
1. Look at the routing structure (pages/, app/, or routes/)
2. Find modal/dialog components
3. Identify settings pages and their sections
4. Look for admin-only features
5. Find any existing navigation patterns

Then ask me about:
- What are the main features of my app?
- What user roles exist?
- What tools do users ask about most often?
- Does my app have APIs where the AI agent should create, update, or delete resources?
- Are there queries or lookups the AI should validate before committing changes?

Registering Tools

Co-locate your tool metadata with its handler — the CLI automatically discovers these definitions via AST scanning.

examples/guides/tools/use-pillar-tool-basic.tsx
import { usePillarTool } from "@pillar-ai/react";
import { useRouter } from "next/navigation";
export function usePillarTools() {
const router = useRouter();
// Basic navigation tool
usePillarTool({
name: "open_dashboard",
type: "navigate",
description: "Navigate to the main dashboard",
examples: ["go to dashboard", "show me the dashboard", "open home"],
execute: () => {
router.push("/dashboard");
},
});
// Tool with input schema for data extraction
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"],
},
examples: ["show user 123's profile", "view profile for john@example.com"],
execute: ({ userId }) => {
router.push(`/users/${userId}`);
},
});
// Auto-run tool (executes without confirmation)
usePillarTool({
name: "toggle_dark_mode",
type: "trigger_tool",
description: "Toggle between light and dark theme",
autoRun: true,
examples: ["switch to dark mode", "turn on light theme"],
execute: () => {
document.documentElement.classList.toggle("dark");
},
});
}

Multiple Tools

Register multiple related tools in a single call:

examples/guides/tools/use-pillar-tool-multiple.tsx
import { usePillarTool } from "@pillar-ai/react";
import { useRouter } from "next/navigation";
export function useNavigationTools() {
const router = useRouter();
// Register multiple related tools in a single call
usePillarTool([
{
name: "open_billing",
type: "navigate",
description: "Navigate to billing and subscription settings",
examples: ["go to billing", "view my subscription", "payment settings"],
execute: () => router.push("/settings/billing"),
},
{
name: "open_team",
type: "navigate",
description: "Navigate to team management page",
examples: ["manage team", "invite team members", "team settings"],
execute: () => router.push("/settings/team"),
},
{
name: "open_integrations",
type: "navigate",
description: "Navigate to integrations and connected apps",
examples: ["view integrations", "connect apps", "api settings"],
execute: () => router.push("/settings/integrations"),
},
]);
}

The CLI scanner finds both usePillarTool / injectPillarTool and defineTool calls when syncing.

Tool Types

TypeDescriptionUse Case
navigateNavigate to a page in your appSettings, dashboard, detail pages
trigger_toolRun custom logicOpen modals, start wizards, toggle features, API calls
queryFetch data and return to the AI agentList items, validate queries, inspect state
inline_uiShow interactive UI in chatForms, confirmations, previews
external_linkOpen URL in new tabDocumentation, external resources
copy_textCopy text to clipboardAPI keys, code snippets

Tool Properties

examples/guides/tools/tool-properties.ts
usePillarTool({
// Required
name: string, // Unique identifier for this tool
description: string, // AI uses this to match user intent
execute: (input) => void, // Your execution logic
// Optional
type?: ToolType, // navigate, trigger_tool, query, inline_ui, external_link, copy_text
guidance?: string, // Per-tool instructions for the AI agent
examples?: string[], // Example phrases that trigger this tool
inputSchema?: { // JSON Schema for extracting data from user queries
type: "object",
properties: Record<string, unknown>,
required?: string[],
},
outputSchema?: { // JSON Schema for output fields (supports sensitive: true)
type: "object",
properties: Record<string, unknown>,
},
autoRun?: boolean, // Execute without user clicking (default: false)
autoComplete?: boolean, // Mark done immediately (default: true)
requiredContext?: object, // Context required for tool to be available
webMCP?: boolean, // Also register with WebMCP (navigator.modelContext)
});

Handler Return Values

The execute handler returns data to the agent. Throw for errors:

examples/guides/tools/handler-return-values.tsx
usePillarTool({
name: "my_tool",
description: "Example tool showing return values",
execute: async () => {
// Success — return flat data (what you return is what the agent sees)
return { message: "Invite sent!" };
// Failure — throw an error (SDK catches it and reports to the agent)
throw new Error("Something went wrong");
},
});

Wizard Tools

For tools that open multi-step flows (wizards, modals with multiple steps), signal completion when the user finishes:

examples/guides/tools/wizard-complete-tool.tsx
import { usePillarTool, usePillar } from "@pillar-ai/react";
export function useOnboardingTool() {
const { completeTool } = usePillar();
usePillarTool({
name: "start_onboarding",
type: "trigger_tool",
description: "Start the onboarding wizard",
execute: () => {
openOnboardingWizard({
onComplete: () => {
// Signal to Pillar that the tool is done
completeTool();
},
});
// Don't return success yet - wizard handles completion
},
});
}

For simple tools (navigation, toggles), the SDK handles completion automatically when your handler returns. You only need to call completeTool() for flows where the SDK can't know when the user is "done"—like multi-step wizards or forms that require user input.

Data Extraction

Define an inputSchema to have the AI extract structured data from user messages:

examples/guides/tools/data-extraction-schema.tsx
usePillarTool({
name: "add_source",
type: "trigger_tool",
description: "Add a new knowledge source",
inputSchema: {
type: "object",
properties: {
url: { type: "string", description: "URL of the source to add" },
name: { type: "string", description: "Display name for the source" },
},
required: ["url", "name"],
},
execute: ({ url, name }) => {
openAddSourceWizard({
prefillUrl: url,
prefillName: name,
});
},
});

Now when a user says "Add my docs site at docs.example.com", the AI extracts the URL and passes it to your handler.

Query Tools

Use type: 'query' for tools that fetch data and return it to the AI agent. The return value of execute is sent back to the agent for further reasoning.

tsx
usePillarTool({
name: 'list_projects',
description: 'List all projects the user has access to',
type: 'query',
autoRun: true,
autoComplete: true,
execute: async () => {
const projects = await api.getProjects();
return projects.map(p => ({ id: p.id, name: p.name, status: p.status }));
},
});

All tools defined with usePillarTool or defineTool return data to the agent by default -- whatever your execute handler returns is what the agent sees:

tsx
usePillarTool({
name: 'create_project',
description: 'Create a new project',
type: 'trigger_tool',
guidance: 'Returns the new project ID. Use list_projects first to check for name conflicts.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Project name' },
description: { type: 'string', description: 'Project description' },
},
required: ['name'],
},
execute: async (data) => {
const project = await api.createProject(data);
return { project_id: project.id };
},
});

Query tools enable the verify-then-commit pattern: the agent tests or previews data first, inspects the result, and only commits changes when the data looks right. See Agent Guidance for how to configure multi-step workflows.

Tool Guidance

The guidance field gives the AI agent per-tool usage instructions. It's different from description:

  • description: For intent matching -- helps the AI find the right tool for a user request
  • guidance: For usage instructions -- tells the AI how to use the tool correctly
tsx
usePillarTool({
name: 'create_chart',
description: 'Create a chart on a dashboard',
guidance:
'Always test the query with test_datasource_query first. ' +
'Only create the chart after the query returns valid data. ' +
'Returns chart_id which can be used to add it to a dashboard.',
type: 'trigger_tool',
inputSchema: { ... },
execute: async (data) => { ... },
});

Use guidance for chaining requirements, common pitfalls, return value descriptions, and when to prefer one tool over another.

Context-Based Tool Filtering

Use requiredContext to control which tools the AI suggests based on user context. This is useful for:

  • Admin-only tools
  • Feature-gated tools based on user plan
  • Tools that only make sense on certain pages

Admin-Only Tools

examples/guides/tools/admin-only-tool.tsx
usePillarTool({
name: "delete_user",
type: "trigger_tool",
description: "Delete a user from the organization",
requiredContext: { userRole: "admin" },
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "ID of the user to delete" },
},
required: ["userId"],
},
execute: ({ userId }) => deleteUser(userId),
});

This tool will only be suggested when the user's context includes userRole: 'admin'.

Setting Context in Your App

For requiredContext to work, you need to set the user's context:

examples/guides/tools/user-context-sync.tsx
// In your app
import { usePillar } from '@pillar-ai/react';
function UserContextSync() {
const { pillar } = usePillar();
const { user } = useAuth();
useEffect(() => {
if (user && pillar) {
pillar.setContext({
userRole: user.role, // 'admin', 'member', 'viewer', etc.
});
}
}, [pillar, user]);
return null;
}

How It Works

  1. You define tools with requiredContext: { userRole: 'admin' }
  2. Your app calls pillar.setContext({ userRole: user.role })
  3. When searching for tools, Pillar filters out tools where the user doesn't match the required context
  4. Only matching tools are suggested by the AI

Multiple Context Requirements

You can require multiple context fields:

examples/guides/tools/multiple-context-requirements.tsx
usePillarTool({
name: "beta_feature",
type: "trigger_tool",
description: "Access the beta analytics dashboard",
requiredContext: {
plan: "enterprise",
betaAccess: true,
},
execute: () => router.push("/analytics/beta"),
});

See Context API for all available context properties.

Best Practices

Write Clear Descriptions

The AI matches user queries to your tool descriptions. Be specific:

examples/guides/tools/good-descriptions.tsx
// Good - specific about when to use
usePillarTool({
name: "view_billing",
description: "Navigate to billing page. Suggest when user asks about payments, invoices, or subscription.",
// ...
});
// Less helpful - too generic
usePillarTool({
name: "view_billing",
description: "Go to billing",
// ...
});

Add Example Phrases

Help the AI understand different ways users might phrase requests:

examples/guides/tools/example-phrases.tsx
usePillarTool({
name: "invite_member",
description: "Invite a new team member",
examples: [
"invite someone to my team",
"add a new team member",
"how do I add users?",
"share access with someone",
],
execute: () => setShowInviteModal(true),
});

Handle Errors Gracefully

examples/guides/tools/error-handling.tsx
usePillarTool({
name: "export_data",
type: "trigger_tool",
description: "Export all data as CSV",
execute: async () => {
await exportData();
return { message: "Export complete" };
// If exportData() throws, the SDK catches it and
// reports the error to the agent automatically.
},
});

Close Panel When Appropriate

For tools that take over the screen:

examples/guides/tools/close-panel.tsx
import { usePillarTool, usePillar } from "@pillar-ai/react";
export function useTourTool() {
const { close } = usePillar();
usePillarTool({
name: "start_tour",
type: "trigger_tool",
description: "Start the onboarding tour",
execute: () => {
close();
startOnboardingTour();
},
});
}

Generic Event Handlers

For tools that don't fit the usePillarTool pattern (e.g., handling events from the backend, generic routing), you can use pillar.on() or pillar.onTask():

examples/guides/tools/provider-on-task.tsx
import { usePillar } from "@pillar-ai/react";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export function useGenericHandlers() {
const { on, onTask } = usePillar();
const router = useRouter();
useEffect(() => {
// Handle any navigate tool dynamically
onTask("navigate", (data) => {
router.push(data.path);
});
// Handle backend-triggered events
on("session:updated", (session) => {
console.log("Session updated:", session);
});
}, [on, onTask, router]);
}

This is useful for:

  • Handling dynamic tool names
  • Responding to backend-triggered events
  • Legacy handler patterns during migration

Next Steps