CopilotKit SDK: A Practical Guide to Building In-App AI Copilots

If you want to embed an AI assistant directly inside your web application — one that can read your app's state, take actions on behalf of users, and answer questions about your actual data — the CopilotKit SDK is one of the most direct ways to do it. Rather than bolting a chat widget onto the side of your product, CopilotKit gives you React components and hooks that connect a large language model to your application's UI, state, and backend logic. This guide explains the architecture and shows the legacy v1 integration for developers maintaining an existing React or Next.js application. It is not a tutorial for the current v2 API.

The core problem CopilotKit solves is a familiar one: LLMs are powerful, but wiring them into a real application means handling streaming responses, tool calling, UI rendering, state access, and security boundaries yourself. CopilotKit packages that plumbing so you can focus on defining what your copilot can do inside your app.

What the CopilotKit SDK Actually Provides

For the self-hosted React architecture discussed here, CopilotKit has a frontend layer and a backend runtime. The current ecosystem also supports other frontend and agent integrations.

On the frontend, you get React components and hooks. The most visible pieces are UI components like CopilotSidebar or CopilotPopup, which render a ready-made chat interface in your app. Underneath, hooks such as useCopilotReadable and useCopilotAction are where the real integration happens. useCopilotReadable exposes application state to the LLM as context — for example, the current contents of a spreadsheet, a list of tasks, or a selected record. useCopilotAction defines actions the model can call, which is how the copilot stops being a chatbot and starts being an agent that manipulates your app.

On the backend, the CopilotKit Runtime acts as a bridge between your frontend and an LLM provider such as OpenAI, Anthropic, or Azure OpenAI. Your API keys stay on the server side; the browser never sees them. The runtime translates frontend requests into provider calls and streams results back.

A minimal mental model:

  • The copilot UI collects user intent.
  • Readable state gives the model context about your app.
  • Actions give the model hands — functions it can invoke to change things.
  • The runtime routes everything securely between browser and model provider.

This architecture matters because it separates concerns cleanly: you decide what the model can see and do, and the SDK handles streaming, tool-call protocol details, and UI rendering.

Getting Started: A Minimal React Setup

The snippets in this section target the legacy v1 API surface in CopilotKit 1.50.0. The official 1.50 release notes describe compatibility with earlier v1 client code. CopilotKit's runtime documentation distinguishes the legacy endpoint factories from the newer v2 handlers. For a new project, follow the current quickstart linked from that page instead of adopting this legacy setup.

For this version-specific walkthrough, installation covers three packages: two React packages and the backend runtime. Use an existing Next.js App Router application with React and TypeScript configured. Keep the three CopilotKit packages aligned and commit the generated lockfile:

npm install --save-exact @copilotkit/react-core@1.50.0 @copilotkit/react-ui@1.50.0 @copilotkit/runtime@1.50.0

Next, wire up the runtime as an API route. In Next.js, that's typically a file at app/api/copilotkit/route.ts:

import {
  CopilotRuntime,
  OpenAIAdapter,
  copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";

export const runtime = "nodejs";
const copilotRuntime = new CopilotRuntime();
const model = process.env.COPILOT_MODEL;
if (!model || !process.env.OPENAI_API_KEY) {
  throw new Error("Configure COPILOT_MODEL and OPENAI_API_KEY on the server");
}
const serviceAdapter = new OpenAIAdapter({ model });

export const POST = async (req: Request) => {
  const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
    runtime: copilotRuntime,
    serviceAdapter,
    endpoint: "/api/copilotkit",
  });

  return handleRequest(req);
}

Version scope: This single-route handler belongs to the legacy v1 API. Do not combine it with the v2 multi-route handler configuration.

Set OPENAI_API_KEY and COPILOT_MODEL in server-only environment variables; choose an OpenAI model your account can access that supports the tool-calling workflow. The v1 OpenAI adapter reference documents its model option and Azure configuration. Other providers require their own supported adapters and settings, not just a different key. This is a local integration example, not a production security configuration.

On the frontend, put the provider in a client wrapper, for example app/copilot-provider.tsx, and render it around your application content inside the root layout. Keep the root layout's required HTML and body elements. Import the CSS once:

"use client";

import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";

export default function CopilotProvider({ children }: { children: React.ReactNode }) {
  return (
    <CopilotKit runtimeUrl="/api/copilotkit">
      <CopilotSidebar
        defaultOpen={true}
        instructions="Help with the tasks exposed by this application."
      >
        {children}
      </CopilotSidebar>
    </CopilotKit>
  );
}

With valid provider credentials, model access and matching dependencies, this connects the chat panel to the runtime. It still has no application-specific context. The next step is what makes it genuinely useful.

Giving the Copilot Context and Actions

This is the heart of any CopilotKit integration. Two hooks do most of the work.

First, expose state the model should understand. Imagine a task-management app:

"use client";

import { useCopilotReadable } from "@copilotkit/react-core";

type Task = { id: string; title: string; dueDate?: string };

export function TaskList({ tasks }: { tasks: Task[] }) {
  useCopilotReadable({
    description: "The user's current task list",
    value: tasks,
  });

  return (
    <ul>
      {tasks.map((t) => (
        <li key={t.id}>{t.title}</li>
      ))}
    </ul>
  );
}

Now the copilot can answer questions like "which of my tasks are overdue?" because it can see the task data. The value is serialized and passed as context on each relevant request, so be mindful of size — dumping an entire large dataset into context is a common mistake. Expose summaries, filtered views, or the currently selected item instead of everything.

Second, define actions the model can perform:

"use client";

import { useCopilotAction } from "@copilotkit/react-core";

type Priority = "low" | "medium" | "high";
type Props = {
  addTask: (title: string, priority: Priority) => Promise<void>;
};

export function TaskActions({ addTask }: Props) {
  useCopilotAction({
    name: "addTask",
    description: "Adds a new task to the user's list",
    parameters: [
      { name: "title", type: "string", required: true },
      { name: "priority", type: "string", required: true },
    ],
    handler: async ({ title, priority }) => {
      if (!title.trim() || title.length > 200) throw new Error("Invalid title");
      if (priority !== "low" && priority !== "medium" && priority !== "high") {
        throw new Error("Invalid priority");
      }
      await addTask(title.trim(), priority);
    },
  });
  return null;
}

Render TaskActions beneath the provider from a client component. Its addTask prop is an application-owned async function that calls your authenticated backend; implement persistence and permission checks there. Hooks must run inside React components or custom hooks, not at module scope.

With this in place, a user can type "add a high-priority task to review the Q3 budget" and the model can request your addTask action with extracted parameters; successful execution still depends on validation and backend permissions. This is standard LLM tool calling under the hood, but SDK tool definitions do not replace application validation. Check both the arguments and the authenticated user on the backend before changing data.

A particularly useful pattern is the generative UI capability: an action can render custom React components instead of just returning data, letting the copilot display forms, cards, or charts inline in the conversation. This turns the chat from a text stream into a genuine part of your application's interface.

Production Considerations and Common Mistakes

Moving from a local demo to production is where most of the real decisions live.

Security and authorization. Never assume the model's intent is a substitute for permission checks. Every action handler should validate the user's rights server-side before mutating data. If your action calls deleteRecord, the backend endpoint it hits must enforce authentication and authorization independently of the copilot. Treat copilot actions exactly like any other API surface — because that's what they are.

Context size and cost. Every useCopilotReadable value adds tokens to requests. A frequent mistake is registering large, unchanging state globally when only a small slice is relevant. Scope readable context to the components that need it, and prefer compact representations (IDs plus labels rather than full objects).

Prompt design. In this v1 example, put the instructions prop on the chat UI component, as shown on CopilotSidebar, rather than on the CopilotKit provider. The v1 chat reference lists this UI setting. Instructions guide the assistant's answers but are not an authorization boundary. A model may describe operations that were never registered, and an agent backend may have tools beyond frontend actions. Enforce the allowed operations in your own server code.

Choosing when not to use it. CopilotKit shines when your app has meaningful state and actions for the model to work with — dashboards, editors, CRMs, admin panels, data tools. If your product is a content site where the only plausible AI feature is "summarize this article," a simpler direct API integration is probably less overhead. The SDK earns its keep when the copilot needs to operate the app, not just talk about it.

Framework support. CopilotKit's React components mean it fits React and Next.js projects naturally. If your stack is Vue, Svelte, or plain server-rendered templates, verify current framework support in the official documentation before committing, since the component ecosystem is React-centric.

How CopilotKit Compares to Building It Yourself

The alternative to any SDK is a DIY integration: call the OpenAI or Anthropic API directly, implement streaming, handle tool calls, and build your own chat UI. That route gives you more control over the integration, and for a simple Q&A assistant it's genuinely reasonable — a streaming completion endpoint plus a textarea can be built in an afternoon.

The calculus changes when you want agentic behavior. Supporting tool calling with argument validation, rendering tool results as UI, managing conversation state across components, and keeping provider keys secure is a meaningful amount of code. CopilotKit also integrates with agent frameworks like LangGraph and CrewAI, letting you connect a Python-based multi-step agent to a React frontend without writing your own transport layer — something a hand-rolled integration would require significant effort to replicate.

The tradeoff is dependency risk and abstraction. You're adopting the SDK's component APIs and runtime conventions, and you'll track its release cycle. For teams that want in-app copilots as a product feature rather than a one-off experiment, that tradeoff usually favors the SDK. For a single, fixed chat feature with no app interaction, rolling your own remains defensible.

Frequently Asked Questions

Is CopilotKit free to use?

The open-source SDK can be self-hosted, but operating it is not cost-free: budget for hosting, model-provider usage and any optional managed services. CopilotKit also offers hosted capabilities with separate terms. Check the license and pricing for the components you actually deploy.

Does the CopilotKit SDK work with Next.js App Router?

Yes. This guide illustrates a legacy v1 Next.js App Router handler. Current v2 integrations use different imports and handler configuration, so follow the matching runtime documentation. React components and hooks that require client-side context belong in client components marked with “use client”.

Can the copilot access my database directly?

Not automatically. In the frontend pattern shown here, useCopilotReadable exposes selected state and useCopilotAction registers application handlers. A connected backend agent can have additional tools, so access depends on the entire integration. Keep database credentials server-side and require authorization for every query or mutation.

Which LLM providers are supported?

This walkthrough configures the legacy v1 OpenAI adapter. Other providers and Azure deployments need compatible adapters or backend-agent configuration for the API version you use. Verify the relevant provider reference rather than assuming every OpenAI-shaped endpoint supports the same features.

Can I customize the chat UI instead of using the built-in sidebar?

Yes. The built-in components like `CopilotSidebar` and `CopilotPopup` are styled and themable, but you can also build a fully custom interface using the lower-level hooks and message data, rendering the conversation however your design system requires.

Conclusion

The CopilotKit SDK turns "add an AI assistant" from a vague ambition into a concrete engineering task: expose readable state, define actions, secure the handlers, and ship. Its main value is that the copilot becomes a genuine part of your application — able to see real data and perform real operations — rather than a chat box bolted onto the corner. The main caution is to treat every action as an API endpoint with proper authorization, and to keep exposed context small and purposeful. A good next step is to clone one of the official CopilotKit examples, wire it to your own data model with a single readable hook and one action, and see how the interaction feels before planning a broader rollout.



Transparency Note: Links in this article are not affiliate links. I recommend these tools and platforms based on their relevance to the topic, and no compensation is received for including them.

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Necessary

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.

Analytics

This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.

Keeping this cookie enabled helps us to improve our website.