Chapter 199 · Omnibus Instrument Product Analytics
Subchapter 199.16
references/EXAMPLE-javascript-web.mdMarkdown12 KBView on GitHub
Repository: https://github.com/PostHog/context-mill Path: basics/javascript-web
A simple browser-based todo application built with vanilla JavaScript and Vite, demonstrating PostHog integration for non-framework JavaScript projects.
This example serves as:
posthog.init() with api_host configurationposthog.capture() calls with event propertiesposthog.identify() on login and posthog.reset() on logoutposthog.captureException() for unhandled errors and promise rejectionsnpm install# Copy environment template
cp .env.example .env
# Edit .env and add your PostHog project token
# VITE_POSTHOG_PROJECT_TOKEN=phc_your_project_token_here
# VITE_POSTHOG_HOST=https://us.i.posthog.comnpm run devOpen http://localhost:3000 (opens in a new tab) in your browser.
The app tracks these custom events in PostHog (in addition to autocaptured clicks and pageviews):
| Event | Properties | Purpose |
|---|---|---|
todo_added | todo_id, text_length, total_todos | When user adds a new todo |
todo_completed | todo_id, time_to_complete_hours | When user completes a todo |
todo_deleted | todo_id, was_completed | When user deletes a todo |
user_logged_in | (none) | When user logs in |
user_logged_out | (none) | When user logs out |
basics/javascript/
├── index.html # Entry HTML page
├── package.json # Dependencies (posthog-js, vite)
├── vite.config.js # Vite configuration
├── .env.example # Environment variable template
├── .gitignore # Git ignore rules
├── README.md # This file
└── src/
├── posthog.js # PostHog initialization (import this first)
├── main.js # Todo app logic with event tracking
└── style.css # App stylesimport posthog from 'posthog-js'
posthog.init('your-project-token', {
api_host: 'https://us.i.posthog.com',
})Initialize PostHog once, early in your app. All other modules import the same instance.
// Track events with properties — never send PII or user-generated content
posthog.capture('event_name', {
item_count: 5, // Metadata is OK
action_type: 'create', // Categories are OK
})// On login — links events to a known user
posthog.identify('user_123')
// On logout — resets to a new anonymous distinct_id
posthog.reset()// Global error handlers
window.addEventListener('error', (event) => {
posthog.captureException(event.error)
})
window.addEventListener('unhandledrejection', (event) => {
posthog.captureException(event.reason)
})The app works fine without PostHog configured. You’ll see a console warning but the app continues to function normally.
posthog.isFeatureEnabled('flag-key')# PostHog Configuration
VITE_POSTHOG_PROJECT_TOKEN=phc_your_project_token_here
VITE_POSTHOG_HOST=https://us.i.posthog.com
# Optional: Enable debug mode to see PostHog requests in console
# VITE_POSTHOG_DEBUG=true
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo App - PostHog JavaScript Example</title>
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<div id="app">
<header>
<h1>Todo App</h1>
<div id="auth-section">
<div id="logged-out">
<input type="text" id="username-input" placeholder="Enter username" />
<button id="login-btn">Log In</button>
</div>
<div id="logged-in" hidden>
<span id="username-display"></span>
<button id="logout-btn">Log Out</button>
</div>
</div>
</header>
<main>
<form id="todo-form">
<input type="text" id="todo-input" placeholder="What needs to be done?" required />
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<div id="stats">
<span id="total-count">0 items</span>
<span id="completed-count">0 completed</span>
</div>
</main>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
/**
* Simple Todo App with PostHog Analytics
*
* A minimal vanilla JavaScript application demonstrating PostHog integration
* for non-framework browser JavaScript projects.
*/
import posthog from './posthog.js';
// --- State ---
let todos = JSON.parse(localStorage.getItem('todos') || '[]');
let currentUser = localStorage.getItem('currentUser') || null
/**
* PostHog initialization for vanilla JavaScript.
*
* Initializes posthog-js once and exports the instance for use across the app.
* This file should be imported before any other modules that call PostHog methods.
*/
import posthog from 'posthog-js';
const apiKey = import.meta.env.VITE_POSTHOG_PROJECT_TOKEN;
const apiHost = import.meta.env.VITE_POSTHOG_HOST || 'https://us.i.posthog.com';
if (!apiKey) {
console.warn(
'PostHog not configured (VITE_POSTHOG_PROJECT_TOKEN not set).',
'App will work but analytics will not be tracked.',
);
} else {
posthog.init(apiKey, {
api_host: apiHost,
// Autocapture is ON by default — tracks clicks, form submissions, pageviews
// capture_pageview: true (default) — captures $pageview on init
// For SPAs with History API routing, use: capture_pageview: 'history_change'
});
}
export default posthog;
import { defineConfig } from 'vite';
export default defineConfig({
server: {
port: 3000,
},
});