Quick start

Canopy takes about a minute to install. Add your site, paste one script tag (or mount one React component), and pageviews start flowing to your own domain — no cookies, no consent banner, no third-party requests. This page covers the three install paths and how to verify data is arriving.

1Add your site

Open the dashboard and create a site for the domain you want to measure — for example example.com. Canopy normalizes the domain (lowercase, no protocol, no www.) and derives your site ID from it:

site idauto-generated
example.com  →  canopy_example_com

The site ID is the only identifier the tracker needs. You can find it any time in your site settings, and it appears in the install snippets the dashboard generates for you.

2HTML script — any site

This is the universal install. It works on plain HTML, WordPress, Webflow, Ghost, Rails, Astro, or any SPA framework — anywhere you can add a tag to the <head>. The script is under 2kb gzipped, loads async, and never blocks rendering:

HTML • p.jsasync • <2kb
<script
  src="https://your-canopy.app/p.js"
  data-site-id="canopy_example_com"
  async
></script>

Replace your-canopy.app with the host where Canopy runs and canopy_example_com with your site ID. Once loaded, the script initializes itself: it sends a pageview on load, keeps sending pageviews as visitors navigate (including SPA route changes), and tracks clicks on outbound links. If Canopy runs on a different origin than your site, point events at it explicitly:

cross-origin installdata-endpoint
<script
  src="https://canopy.your-app.com/p.js"
  data-site-id="canopy_example_com"
  data-endpoint="https://canopy.your-app.com/api/canopy/event"
  async
></script>

Attributes

The script reads its configuration from data-* attributes on the tag:

AttributeRequiredDescription
data-site-idyesYour site ID, e.g. canopy_example_com. Without it the script does nothing.
data-endpointnoWhere events are POSTed. Defaults to /api/canopy/event on the serving origin.
data-envnoOptional label attached to every event, e.g. production or staging.
data-respect-dntnoSet to false to track visitors with Do Not Track enabled. DNT is respected by default.
data-disable-outboundnoPresence of the attribute disables automatic outbound click tracking.

3Next.js — @wnstn/canopy-nextjs

On Next.js, prefer the React SDK over the script tag. It ships as its own package (it depends on the core tracker for you) and adds a context provider, hooks, and SPA-aware pageviews wired into the App Router:

terminalone dep
npm i @wnstn/canopy-nextjs

Wrap your root layout once — that single mount replaces the script tag entirely:

app/layout.tsxapp router
import { CanopyProvider } from "@wnstn/canopy-nextjs";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <CanopyProvider siteId={process.env.NEXT_PUBLIC_CANOPY_SITE_ID!} />
        {children}
      </body>
    </html>
  );
}

The provider initializes the tracker on the client, sends the first pageview automatically, and keeps pageviews in sync as users navigate. From any client component, track events with the hook:

any componentclient
'use client';
import { useCanopyTrack } from "@wnstn/canopy-nextjs";

export function PricingButton() {
  const { track } = useCanopyTrack();
  return <button onClick={() => track("signup", { plan: "pro" })}>Upgrade</button>;
}
note
Hooks must run in client components — the tracker only exists in the browser. Server components keep rendering untouched; put tracking calls behind 'use client or event handlers. Full provider options and the hook list live in the SDK reference.

4Vanilla JS — framework agnostic

If you bundle your own JavaScript and don't want the script tag, install the core tracker and initialize it directly. Calls made before initialization are queued and replayed once the tracker starts, so import order never matters:

vanillanpm i @wnstn/canopy
import { createCanopy, track } from "@wnstn/canopy";

createCanopy({ siteId: "canopy_example_com" });

// anywhere in your app — even before createCanopy runs
track("checkout", { value: 49, currency: "USD" });

5Verify it works

Data should appear within seconds of loading a page. Three quick checks:

1. Open your site, then the browser network tab — you should see a request to /api/canopy/event returning 202.
2. Open Realtime — you'll see yourself as an active visitor almost immediately.
3. Check Overview for pageviews, referrers, and the live visitor count.
not seeing events?
The three usual suspects: the browser has Do Not Track enabled (Canopy drops events by default), an unknown site ID returns 404 in production, or an extension is blocking requests. The API error table maps each status code to its cause.

6Add custom events when ready

Pageviews and outbound clicks are automatic — everything else is one call away. Events carry arbitrary JSON properties that land in your database as JSONB, ready to filter in the event log:

custom eventsprops are yours
track("signup", { plan: "pro", source: "header" });
track("checkout", { value: 49, currency: "USD" });
track("feature_used", { name: "export", format: "csv" });

Recipes, naming rules, and server-side events are covered in the custom events guide.

Ready to measure?

Create your first site, paste one line, see live visitors.