Which package?
| Option | Type | Description |
|---|---|---|
| @codeskop/tracker | any website | The SDK itself, with no framework dependency. Exports init, identify, reset, recordException, setEnabled and flush. Use it on its own for plain JavaScript, Vue, Svelte, Angular or any other site. |
| @codeskop/tracker-react | React 18 / 19 | A small React layer on top: CodeskopProvider (calls init for you), CodeskopErrorBoundary (reports render errors) and the useCodeskop() hook. It uses @codeskop/tracker under the hood. |
For React apps, install both, so your package manager (including pnpm's strict mode) lets you import from either:
npm install @codeskop/tracker @codeskop/tracker-reactOther sites only need the core package:
// main.ts: run once, as early as possibleimport { init, identify } from "@codeskop/tracker"; init({ apiKey: import.meta.env.VITE_CODESKOP_KEY }); // later, after loginidentify(user.id);Initialize once, with the Provider or init(), not both
Every call toinit() replaces the running SDK, including the identified user. In React, let CodeskopProvider do it, and don't also call init() yourself.Example
import { init } from "@codeskop/tracker"; init({ apiKey: "cs_live_pk_YOUR_KEY", // required endpoint: "https://api.codeskop.com", // default release: "1.4.0", captureNetwork: true, // fetch/XHR capture captureErrors: true, // global error handlers redactHeaders: ["authorization", "cookie", "x-session-id"], maxQueueMb: 5,});Options
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | Your public ingest key, cs_live_pk_… or cs_test_pk_…. A secret or malformed key leaves the SDK switched off. |
| endpoint | string | https://api.codeskop.com | Where events are sent. You shouldn't need to change this. |
| release | string | Not set | Your app's version. Attached to every event so you can tell which release an issue started in. |
| captureNetwork | boolean | true | Capture fetch and XMLHttpRequest calls: timing for every request, plus an error event for network failures and HTTP 4xx/5xx. |
| capturePageViews | boolean | true | Product analytics (Growth and Scale): record the first page and every navigation as a screen, path only. Since 1.3. See Track events & screens. |
| captureErrors | boolean | true | Capture uncaught errors and unhandled promise rejections. recordException works either way. |
| redactHeaders | string[] | ["authorization", "cookie"] | Header names that are always dropped. authorization and cookie are always dropped anyway; add your own sensitive headers here. |
| maxQueueMb | number | 5 | Maximum size of the on-device event queue, in MB. When it's full, the oldest routine events are dropped first. The project's offline queue size in SDK settings replaces it once fetched (1.2+). |
| environment | string | "production" | Accepted for forward compatibility, but not sent today. Separate environments with test and live keys instead. |
| sampleRates | Record<string, number> | {} | Share of each event type to keep, e.g. { api_timing: 0.2 }. Errors and failed requests are always kept. Used until the project's SDK settings are fetched, which then replace it (1.2+). Earlier versions keep every event. |
Common setups
Errors only, no network capture
init({ apiKey: "cs_live_pk_YOUR_KEY", captureNetwork: false, // don't patch fetch/XHR});Extra sensitive headers
Only a short list of safe response headers is ever captured (content-type, content-length, content-encoding, accept, cache-control). redactHeaders can only narrow that list further; it can never make the SDK capture more.
React
With Vite, Create React App or any client-rendered React app, wrap the root once:
// main.tsximport { createRoot } from "react-dom/client";import { CodeskopProvider, CodeskopErrorBoundary } from "@codeskop/tracker-react"; createRoot(document.getElementById("root")!).render( <CodeskopProvider config={{ apiKey: import.meta.env.VITE_CODESKOP_KEY, release: "1.4.0" }}> <CodeskopErrorBoundary fallback={<p>Something went wrong.</p>}> <App /> </CodeskopErrorBoundary> </CodeskopProvider>,);The provider reads config once, on its first render. More in React.
Next.js (App Router)
From version 1.1, the React package ships a "use client" directive, so you can use CodeskopProvider and CodeskopErrorBoundary straight from app/layout.tsx. To identify signed-in users, add a small client component inside the provider:
// app/layout.tsx: a Server Component, no wrapper needed (1.1+)import { CodeskopProvider, CodeskopErrorBoundary } from "@codeskop/tracker-react";import { IdentifyUser } from "./identify-user"; export default async function RootLayout({ children }: { children: React.ReactNode }) { const user = await getCurrentUser(); // your own auth return ( <html lang="en"> <body> <CodeskopProvider config={{ apiKey: process.env.NEXT_PUBLIC_CODESKOP_KEY!, release: process.env.NEXT_PUBLIC_APP_VERSION }} > <IdentifyUser userId={user?.id} /> <CodeskopErrorBoundary fallback={<p>Something went wrong.</p>}>{children}</CodeskopErrorBoundary> </CodeskopProvider> </body> </html> );}// app/identify-user.tsx"use client"; import { useEffect } from "react";import { useCodeskop } from "@codeskop/tracker-react"; export function IdentifyUser({ userId }: { userId?: string }) { const { identify, reset } = useCodeskop(); useEffect(() => { if (userId) identify(userId); else reset(); }, [userId, identify, reset]); return null;}- The SDK only starts in the browser. Nothing runs during server rendering, so no
typeof windowchecks are needed. - Pages Router: wrap the app in
pages/_app.tsxthe same way.
Still on 1.0.x?
Version 1.0.x has no"use client" directive: importing it into a Server Component fails the build with createContext is not a function. Upgrade with npm install @codeskop/tracker@latest @codeskop/tracker-react@latest, or import it from a file that starts with "use client".Report your app version
Pass your app's version as release. It shows as the app version on the Users tab and on issues, and powers release health. The easiest source is the version in your package.json; npm exposes it to build scripts as npm_package_version:
// next.config.ts: expose package.json's version to the browserconst nextConfig = { env: { NEXT_PUBLIC_APP_VERSION: process.env.npm_package_version },};export default nextConfig;// vite.config.tsexport default defineConfig({ define: { __APP_VERSION__: JSON.stringify(process.env.npm_package_version) },}); // main.tsxinit({ apiKey: import.meta.env.VITE_CODESKOP_KEY, release: __APP_VERSION__ });npm_package_version is set when you build through an npm, yarn or pnpm script (npm run build). In CI you can use a git tag or commit SHA instead.
API key and environment variables
Use a public key (cs_live_pk_… for production, cs_test_pk_… for test). It's meant to be visible in browser code. Never put a secret key (…_sk_…) in a web app; the SDK refuses it and stays off. Browser code only sees variables your bundler exposes:
| Option | Type | Description |
|---|---|---|
| Next.js | NEXT_PUBLIC_CODESKOP_KEY | Read with process.env.NEXT_PUBLIC_CODESKOP_KEY. Inlined at build time. Restart next dev and rebuild after changing it. |
| Vite | VITE_CODESKOP_KEY | Read with import.meta.env.VITE_CODESKOP_KEY. |
| Create React App | REACT_APP_CODESKOP_KEY | Read with process.env.REACT_APP_CODESKOP_KEY. |
Common problems
| Option | Type | Description |
|---|---|---|
| “createContext is not a function” / “only works in Client Components” | n/a | You're on @codeskop/tracker-react 1.0.x, which has no "use client" directive. Upgrade to 1.1 or later; see Next.js. |
| Nothing shows up in the dashboard | n/a | The key is missing or isn't public. Log process.env.NEXT_PUBLIC_CODESKOP_KEY in the browser to check it's defined, and use a _pk_ key. A cs_test_ key reports to the Test environment: switch the dashboard to Test to see it. |
| Requests to api.codeskop.com are blocked | n/a | A Content-Security-Policy is blocking them. Allow the endpoint in connect-src: Content-Security-Policy: connect-src 'self' https://api.codeskop.com; |
| “Module not found: @codeskop/tracker” | n/a | You import the core package directly but only installed the React one. Install both. |
| The user is lost after navigating, or events are duplicated | n/a | init() is being called more than once, for example yourself and through CodeskopProvider, or from a component that remounts. Initialize in one place, at the root. |
| Network calls aren't captured | n/a | Network monitoring is included from the Starter plan. On Free, only errors are captured. Also check you haven't set captureNetwork: false. |
identify() on the first render is ignored (1.0.x)
In 1.0.x the provider started the SDK in an effect, and React runs children's effects first, so an identify() in a child's first effect was dropped. Version 1.1 starts the SDK during the provider's first render, before any child effect runs. Upgrade to fix it.
What you can't configure
Some privacy rules are fixed, on purpose:
- Request and response bodies are never captured.
- URL query strings and fragments are always dropped; only the host and path are kept.
- Error messages are replaced with
[REDACTED]; the error type and stack trace are kept.
See Privacy & Security for the full picture.
The kill-switch overrides everything
When the SDK starts, it fetches your project's remote config. If capture is switched off there (or your plan isn't active), the SDK sends nothing, whatever you pass toinit. See Remote Config.