Skip to main content

Installation

Choose your preferred installation method:
Add the script before </body>:
<script
  src="https://api.usecrow.org/static/crow-widget.js"
  data-api-url="https://api.usecrow.org"
  data-product-id="YOUR_PRODUCT_ID"
  data-agent-name="Your Agent Name"
></script>
AttributeRequiredDescription
data-api-urlYeshttps://api.usecrow.org
data-product-idYesYour product ID from dashboard
data-agent-nameNoDisplay name (defaults to “Assistant”)
data-variantNowidget (default) or copilot
data-subdomainNoSubdomain key for multi-subdomain endpoints
data-languageNoISO 639-1 language code (e.g., es, fr, ja). See Multi-Language
data-context-labelNoLabel shown in the widget indicating what page the AI sees. See Context Label
After the script loads, use window.crow() to pass functions and objects:
CommandDescription
setIdentityTokenFetcherAuto-refreshing JWT function. See Identity
setContextPage context object. See Context
onToolResultCallback on tool completion. See Callbacks
registerToolRenderersCustom inline visuals. See Renderers
identifyManual JWT token (alternative to fetcher)
registerToolsClient-side tool handlers
setContextLabelVisible label for current page context. See Context Label
openProgrammatically open/expand the widget or copilot
closeProgrammatically close/collapse the widget or copilot

Find Your Product ID

  1. Go to app.usecrow.ai/deploy
  2. Copy your Product ID

User Identity

Authenticate users for persistent conversations and user-specific actions.
Option A — Auto-refresh (recommended). Pass a function that fetches a JWT. The SDK calls it on mount and re-calls it automatically when the token expires (401):
window.crow('setIdentityTokenFetcher', async () => {
  const res = await fetch('/api/crow-token');
  const { token } = await res.json();
  return token;
});
Option B — Manual. Pass a static token (you handle refresh yourself):
window.crow('identify', {
  token: 'jwt-from-your-backend',  // Required
  name: 'John Doe'                  // Optional
})

// On logout
window.crow('resetUser')
See Identity Verification for backend JWT setup.

Client-Side Tools

Register tools the agent can call. The return value from your handler is sent back to the agent, so it can confirm success or handle errors intelligently. See Client-Side Tools for full setup.
window.crow('registerTools', {
  navigateToPage: async ({ path }) => {
    router.push(path)
    return { success: true }  // Agent sees this result
  },
  
  showUpgradeModal: async () => {
    document.getElementById('upgrade-modal').showModal()
    return { shown: true }
  }
})
Return meaningful results from your tools. The agent uses these to provide accurate feedback like “Done! I’ve navigated you to Settings” or handle errors gracefully.

Page Context

Give the AI context about the current page so it can perform page-aware actions.
window.crow('setContext', { itemId: 'abc-123', page: 'settings' });

// Update on navigation:
window.addEventListener('popstate', () => {
  window.crow('setContext', { page: location.pathname });
});

Context Label

Show users what page context the AI is aware of — a small label displayed at the top of the widget. Unlike context (which is invisible data sent to the AI), contextLabel is a visible indicator for the user.
window.crow('setContextLabel', 'Viewing: Blog Post #123');

// Update on navigation:
window.addEventListener('popstate', () => {
  window.crow('setContextLabel', getPageLabel());
});

// Clear the label:
window.crow('setContextLabel', '');

Tool Result Callbacks

React to server-side tool completions — refresh data, show a diff UI, dispatch events, etc.
window.crow('onToolResult', (toolName, result) => {
  if (toolName === 'propose_revision') {
    showDiffUI(result);
  }
});

Tool Renderers

Render custom visuals inline in the chat when a tool completes — data cards, tables, charts, and more. Your renderer receives { result, status } and returns one of:
Return typeUse case
HTML stringData cards, tables, simple visuals
HTMLElementCharts via Chart.js, D3, ECharts, Plotly
ReactNodeFull React components (React SDK only)

Data Card

Show key metrics with change indicators:
window.crow('registerToolRenderers', {
  query_analytics_chart: ({ result }) => {
    const r = result || {};
    const color = r.changePercent > 0 ? '#059669' : '#ef4444';
    const arrow = r.changePercent > 0 ? '↑' : '↓';
    return `<div style="padding:12px; border:1px solid #e2e8f0; border-radius:8px; background:#fff; font-family:system-ui,sans-serif;">
      <div style="font-size:13px; font-weight:600; color:#0f172a;">${r.title || 'Metric'}</div>
      <div style="font-size:24px; font-weight:700; color:#2563eb; margin-top:4px;">${r.currentValue || 'N/A'}</div>
      ${r.changePercent != null
        ? `<div style="font-size:12px; color:${color};">${arrow} ${Math.abs(r.changePercent).toFixed(1)}%</div>`
        : ''}
      ${r.dashboard_url
        ? `<a href="${r.dashboard_url}" target="_blank" style="font-size:11px; color:#2563eb; text-decoration:none; margin-top:8px; display:inline-block;">View full chart ↗</a>`
        : ''}
    </div>`;
  }
});

Table

Display structured data in rows and columns:
window.crow('registerToolRenderers', {
  query_data_table: ({ result }) => {
    const r = result || {};
    const headers = r.columns || [];
    const rows = r.rows || [];
    return `<div style="border:1px solid #e2e8f0; border-radius:8px; overflow:hidden; font-family:system-ui,sans-serif;">
      <table style="width:100%; border-collapse:collapse; font-size:12px;">
        <thead>
          <tr style="background:#f8fafc;">
            ${headers.map(h =>
              '<th style="padding:8px 12px; text-align:left; font-weight:600; border-bottom:1px solid #e2e8f0;">' + h + '</th>'
            ).join('')}
          </tr>
        </thead>
        <tbody>
          ${rows.map(row =>
            '<tr style="border-bottom:1px solid #f1f5f9;">' +
              row.map(cell =>
                '<td style="padding:6px 12px; color:#334155;">' + cell + '</td>'
              ).join('') +
            '</tr>'
          ).join('')}
        </tbody>
      </table>
    </div>`;
  }
});

Chart (Chart.js, D3, ECharts)

For interactive charts, return an HTMLElement instead of a string. The widget mounts it directly:
<!-- Load Chart.js (or any charting library) -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<script>
window.crow('registerToolRenderers', {
  query_analytics_chart: ({ result }) => {
    const canvas = document.createElement('canvas');
    canvas.style.width = '100%';
    canvas.style.maxHeight = '200px';
    new Chart(canvas, {
      type: 'line',
      data: {
        labels: result.data.map(d => d.date),
        datasets: [{
          label: result.title,
          data: result.data.map(d => d.value),
          borderColor: '#2563eb',
          tension: 0.3,
        }]
      },
      options: { responsive: true, plugins: { legend: { display: false } } }
    });
    return canvas;  // Return the DOM element
  }
});
</script>
Timing: window.crow() must be called after crow-widget.js has loaded. In plain HTML this happens naturally (scripts execute in order). In Next.js, use onLoad:
<Script
  src="https://api.usecrow.org/static/crow-widget.js"
  data-product-id="YOUR_PRODUCT_ID"
  data-api-url="https://api.usecrow.org"
  strategy="afterInteractive"
  onLoad={() => {
    window.crow('registerToolRenderers', { ... });
  }}
/>
The widget uses Shadow DOM for CSS isolation. Use inline style attributes — Tailwind classes and external CSS won’t apply inside the widget.

Programmatic Open / Close

Open or close the widget from your own code — useful for onboarding flows, feature tours, or reacting to in-app events.
window.crow('open');   // expand the widget / sidebar
window.crow('close');  // collapse it
Works with both the floating widget and the copilot sidebar.
window.crow('open') works from anywhere — event handlers, useEffect, timeouts, or third-party callbacks. No imports needed.

Quick Q&A

Some users find it more intuitive to read setup instructions in Q&A format. Here’s everything above as quick answers:
Install the SDK and drop in one component:
npm install @usecrow/client @usecrow/ui
<CrowCopilot
  productId="YOUR_PRODUCT_ID"
  apiUrl="https://api.usecrow.org"
/>
That’s it. Chat works anonymously out of the box. No provider wrapping needed.
Two steps:
  1. Create a backend endpoint (~15 lines) that reads your existing session and mints a JWT signed with your Crow verification secret. Crow never sees your auth system — the JWT is the bridge.
  2. Pass it as a prop:
<CrowCopilot
  productId="YOUR_PRODUCT_ID"
  apiUrl="https://api.usecrow.org"
  getIdentityToken={async () => {
    const res = await fetch('/api/crow-token');
    const { token } = await res.json();
    return token;
  }}
/>
The SDK calls this on mount and auto-refreshes when the token expires. No useEffect, no setInterval, no manual calls.This enables: conversation history, personalized responses, and user-specific tool actions.See Identity Verification for the backend JWT setup.
Pass a context object. It’s reactive — when it changes (e.g., user navigates), the AI automatically knows.
const context = useMemo(() => {
  const match = pathname.match(/\/items\/([^/]+)/);
  return match ? { itemId: match[1] } : {};
}, [pathname]);

<CrowCopilot
  ...
  context={context}
/>
This gets injected into the AI’s system prompt so it can perform page-aware actions like “edit this item” without asking which one.
React SDK — use onToolResult and toolRenderers as props:
<CrowCopilot
  onToolResult={(toolName, result) => {
    if (toolName === 'propose_revision') showDiffUI(result);
  }}
  toolRenderers={{
    query_chart: ({ result }) => <MyChart data={result.data} />,
  }}
/>
Script Tag — use window.crow():
window.crow('onToolResult', (toolName, result) => {
  if (toolName === 'propose_revision') showDiffUI(result);
});

window.crow('registerToolRenderers', {
  query_chart: ({ result }) => `<div>${result.title}: ${result.value}</div>`,
});
Everything is a prop on one component:
<CrowCopilot
  productId="..."
  apiUrl="https://api.usecrow.org"
  getIdentityToken={fetchToken}
  context={{ itemId: "abc-123" }}
  navigate={(path) => router.push(path)}
  onToolResult={(name, result) => ...}
  toolRenderers={{ ... }}
  variant="floating"
  position="right"
/>
No providers, no initializers, no polling.

Troubleshooting

IssueSolution
Widget not appearingCheck Product ID, verify script/component is rendered
Console errorsCheck apiUrl is correct
CORS errorsContact support to whitelist your domain