Send your first event to Pug in under five minutes. By the end you’ll have the Web SDK installed, a page_view flowing into your project, and confirmation in the dashboard.
Prerequisites
- A Pug account: sign up at app.pug.sh (or your self-hosted dashboard)
- Node.js 18+ and a bundler-based web project (Vite, Next.js, etc.)
Building a mobile app? This quickstart is web-only - start with the Flutter setup instead.
Building a backend? Use the server-side Node SDK, or POST events directly from any language - see Integrate over HTTP.
Working with a coding agent? Install with AI has a ready-to-paste prompt for each platform that points the agent at these docs.
1. Create a project and grab your keys
- Sign in to the dashboard.
- Create an organization, then a project inside it.
- Collect the two values this quickstart needs:
- Project ID: in the dashboard URL:
/p/<projectId>. - Public key (
pub_...): under the API Keys section of your project Overview; safe to ship in client code.
- Project ID: in the dashboard URL:
The Web SDK authenticates with your public key. (The same screen also shows a server-side private key - you don’t need it here; see Authentication.)
2. Install the Web SDK
npm install @pug-sh/browserSee Installation for pnpm, yarn, bun, and framework-specific setup. No bundler? Drop in the CDN loader snippet instead.
3. Initialize and track
Call init once at startup, then track anywhere. init registers your project ID and public key and begins auto-tracking (page views, clicks). The first argument to track is the event name - any string you choose.
Vite / React
// src/main.tsx
import { init, track } from '@pug-sh/browser'
init('YOUR_PROJECT_ID', {
apiKey: 'pub_YOUR_PUBLIC_KEY'
})
track('signup', { plan: 'pro' })Next.js App Router
init must run in the browser, so wrap it in a client component:
// components/PugProvider.tsx
'use client'
import { init } from '@pug-sh/browser'
import { useEffect } from 'react'
export function PugProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
init(process.env.NEXT_PUBLIC_PUG_PROJECT_ID!, {
apiKey: process.env.NEXT_PUBLIC_PUG_PUBLIC_KEY!
})
}, [])
return <>{children}</>
}Add PugProvider to your root layout and store the values in .env.local:
NEXT_PUBLIC_PUG_PROJECT_ID=your-project-id
NEXT_PUBLIC_PUG_PUBLIC_KEY=pub_your_public_keyOnly ever expose the public key with
NEXT_PUBLIC_*. The private key stays server-side.
4. Verify in the dashboard
- Open your project in the dashboard.
- Go to Live - the active visitor count should tick up within a few seconds.
- Go to Events -
page_view(auto-tracked on init) and yoursignupevent should appear.
Events are batched client-side and flushed every few seconds, so allow up to ~10 seconds for the first one. Force an immediate send with track('signup', {}, { immediate: true }).
5. Identify a user (optional)
After sign-in, link anonymous activity to a known user. This step needs granted consent - there is no anonymous ID to merge while the default cookieless state is in force, so call optInTracking() once the user has agreed:
import { identify, optInTracking } from '@pug-sh/browser'
optInTracking()
await identify('user-123', { email: 'user@example.com', plan: 'pro' })identify() returns a promise and never throws (invalid input, a missing identifier and transport failures are logged and the promise resolves), so you can await it without a try/catch. On sign-out, call reset() to start a fresh anonymous session. See Identity & sessions.
You’re done when…
-
page_viewandsignupappear in Live and Events - Auto-tracked clicks/scrolls appear (defaults are on)
-
identify()merges pre-login events into a profile (optional - needs granted consent)
Common issues
| Symptom | Likely cause | Fix |
|---|---|---|
| No events in dashboard | Wrong project ID or public key | Re-check the API Keys section of your project Overview |
| Events in console but not dashboard | dryRun: true in init options |
Remove dryRun or set false |
| CORS errors in the browser | Wrong endpoint |
Use https://api.pugs.dev or your self-hosted URL |
| Nothing happens, no errors | init() ran outside the browser (SSR) |
Call init() in a client component / useEffect |
| Events delayed | Client-side batching | Normal (see step 4) - or use { immediate: true } |
identify() does nothing, warns once |
Consent is cookieless (the default) - no stored ID to merge |
optInTracking() after the user agrees; see Tracking consent |
| Event counts look right, unique-user counts look low | Cookieless IDs rotate daily, so unique-user metrics, funnels and retention exclude them by default | Expected before consent; see include_cookieless |
Next steps
Next: Core concepts - orgs, projects, keys, and the event pipeline. Then:
- Initialization: batching, sessions, auto-track options
- Tracking events: custom events and well-known schemas
- Authentication: public vs private keys