` section.
```html title="index.html"
```
That's it! OpenPanel will now automatically track:
* Page views
* Visit duration
* Referrers
* Device and browser information
* Location
## Using a Framework?
If you are using a specific framework or platform, we have dedicated SDKs that provide a better developer experience.
} description="Optimized for App Router and Server Components" />
} description="Components and hooks for React applications" />
} description="Integration for Vue.js applications" />
} description="Universal JavaScript/TypeScript SDK" />
} description="Track mobile apps with React Native" />
} description="Server-side tracking for Python" />
## Explore all SDKs
We support many more platforms. Check out our [SDKs Overview](/docs/sdks) for the full list.
---
## Track Events
URL: https://openpanel.dev/docs/get-started/track-events
Events are the core of OpenPanel. They allow you to measure specific actions users take on your site, like clicking a button, submitting a form, or completing a purchase.
## Tracking an event
To track an event, simply call the `track` method with an event name.
```javascript
op.track('button_clicked');
```
## Adding properties
You can add additional context to your events by passing a properties object. This helps you understand the details of the interaction.
```javascript
op.track('signup_button_clicked', {
location: 'header',
color: 'blue',
variant: 'primary'
});
```
### Common property types
* **Strings**: Text values like names, categories, or IDs.
* **Numbers**: Numeric values like price, quantity, or score.
* **Booleans**: True or false values.
## Using Data Attributes
If you prefer not to write JavaScript, you can use data attributes to track clicks automatically.
```html
```
When a user clicks this button, OpenPanel will automatically track a `signup_clicked` event with the property `location: 'header'`.
---
## How it works
URL: https://openpanel.dev/docs/how-it-works
## Device ID
A **device ID** is a unique identifier generated for each device/browser combination. It's calculated using a hash function that combines:
* **User Agent** (browser/client information)
* **IP Address**
* **Origin** (project ID)
* **Salt** (a rotating secret key)
```typescript
export function generateDeviceId({
salt,
ua,
ip,
origin,
}: GenerateDeviceIdOptions) {
return createHash(`${ua}:${ip}:${origin}:${salt}`, 16);
}
```
### Salt Rotation
The salt used for device ID generation rotates **daily at midnight** (UTC). This means:
* Device IDs remain consistent throughout a single day
* Device IDs reset each day for privacy purposes
* The system maintains both the current and previous day's salt to handle events that may arrive slightly after midnight
```typescript
// Salt rotation happens daily at midnight (pattern: '0 0 * * *')
```
When the salt rotates, all device IDs change, effectively anonymizing tracking data on a daily basis while still allowing session continuity within a 24-hour period.
## Session ID
A **session** represents a continuous period of user activity. Sessions are used to group related events together and understand user behavior patterns.
### Session Duration
Sessions have a **30-minute timeout**. If no events are received for 30 minutes, the session automatically ends. Each new event resets this 30-minute timer.
```typescript
export const SESSION_TIMEOUT = 1000 * 60 * 30; // 30 minutes
```
### Session Creation Rules
Sessions are **only created for client events**, not server events. This means:
* Events sent from browsers, mobile apps, or client-side SDKs will create sessions
* Events sent from backend servers, scripts, or server-side SDKs will **not** create sessions
* If you only track events from your backend, no sessions will be created
Additionally, sessions are **not created for events older than 15 minutes**. This prevents historical data imports from creating artificial sessions.
```typescript
// Sessions are not created if:
// 1. The event is from a server (uaInfo.isServer === true)
// 2. The timestamp is from the past (isTimestampFromThePast === true)
if (uaInfo.isServer || isTimestampFromThePast) {
// Event is attached to existing session or no session
}
```
## Profile ID
A **profile ID** is a persistent identifier for a user across multiple devices and sessions. It allows you to track the same user across different browsers, devices, and time periods.
### Profile ID Assignment
If a `profileId` is provided when tracking an event, it will be used to identify the user. However, **if no `profileId` is provided, it defaults to the `deviceId`**.
This means:
* Anonymous users (without a profile ID) are tracked by their device ID
* Once you identify a user (by providing a profile ID), all their events will be associated with that profile
* The same user can be tracked across multiple devices by using the same profile ID
```typescript
// If no profileId is provided, it defaults to deviceId
if (!payload.profileId && payload.deviceId) {
payload.profileId = payload.deviceId;
}
```
## Client Events vs Server Events
OpenPanel distinguishes between **client events** and **server events** based on the User-Agent header.
### Client Events
Client events are sent from:
* Web browsers (Chrome, Firefox, Safari, etc.)
* Mobile apps using client-side SDKs
* Any client that sends a browser-like User-Agent
Client events:
* Create sessions
* Generate device IDs
* Support full [session tracking](/features/session-tracking)
### Server Events
Server events are detected when the User-Agent matches server patterns, such as:
* `Go-http-client/1.0`
* `node-fetch/1.0`
* Other single-name/version patterns (e.g., `LibraryName/1.0`)
Server events:
* Do **not** create sessions
* Are attached to existing sessions if available
* Are useful for backend tracking without session management
```typescript
// Server events are detected by patterns like "Go-http-client/1.0"
function isServer(res: UAParser.IResult) {
if (SINGLE_NAME_VERSION_REGEX.test(res.ua)) {
return true;
}
// ... additional checks
}
```
The distinction is made in the event processing pipeline:
```typescript
const uaInfo = parseUserAgent(userAgent, properties);
// Only client events create sessions
if (uaInfo.isServer || isTimestampFromThePast) {
// Server events or old events don't create new sessions
}
```
## Timestamps
Events can include custom timestamps to track when events actually occurred, rather than when they were received by the server.
### Setting Custom Timestamps
You can provide a custom timestamp using the `__timestamp` property in your event properties:
```javascript
track('page_view', {
__timestamp: '2024-01-15T10:30:00Z'
});
```
### Timestamp Validation
The system validates timestamps to prevent abuse and ensure data quality:
1. **Future timestamps**: If a timestamp is more than **1 minute in the future**, the server timestamp is used instead
2. **Past timestamps**: If a timestamp is older than **15 minutes**, it's marked as `isTimestampFromThePast: true`
```typescript
// Timestamp validation logic
const ONE_MINUTE_MS = 60 * 1000;
const FIFTEEN_MINUTES_MS = 15 * ONE_MINUTE_MS;
// Future check: more than 1 minute ahead
if (clientTimestampNumber > safeTimestamp + ONE_MINUTE_MS) {
return { timestamp: safeTimestamp, isTimestampFromThePast: false };
}
// Past check: older than 15 minutes
const isTimestampFromThePast =
clientTimestampNumber < safeTimestamp - FIFTEEN_MINUTES_MS;
```
### Timestamp Impact on Sessions
**Important**: Events with timestamps older than 15 minutes (`isTimestampFromThePast: true`) will **not create new sessions**. This prevents historical data imports from creating artificial sessions in your analytics.
```typescript
// Events from the past don't create sessions
if (uaInfo.isServer || isTimestampFromThePast) {
// Attach to existing session or track without session
}
```
This ensures that:
* Real-time tracking creates proper sessions
* Historical data imports don't interfere with session analytics
* Backdated events are still tracked but don't affect session metrics
---
## Beta to V1
URL: https://openpanel.dev/docs/migration/beta-v1
## General
The `Openpanel` class is now called `OpenPanel`!
## Options
* Renamed: `api` to `apiUrl`
* Added: `disabled`
* Added: `filter`
## Methods
* Renamed: `event` method is now called `track`
* Renamed: `setProfile` and `setProfileId` is now called `identify` (and combined)
* Changed: `increment('app_opened', 5)` is now `increment({ name: 'app_opened', value: 5, profileId: '123' })`. So profile ID is now required.
* Changed: `decrement('app_opened', 5)` is now `decrement({ name: 'app_opened', value: 5, profileId: '123' })`. So profile ID is now required.
* Improved: `screenView` method has 2 arguments now. This change is more aligned with `@openpanel/react-native`.
```ts
screenView(properties?: TrackProperties): void;
screenView(path: string, properties?: TrackProperties): void;
// Example
op.screenView('/home', { title: 'Home' }); // path will be "/home"
op.screenView({ title: 'Home' }); // path will be what ever window.location.pathname is
```
## Script tag
* New: `https://openpanel.dev/op1.js` should be used instead of `op.js` (note the filename)
* Renamed: Tracking with attributes have changed. Use `data-track="my_event"` instead of `data-event="my_event"`
## @openpanel/nextjs
* Renamed: `OpenpanelProvider` to `OpenPanelComponent`
* Removed: All exported methods (trackEvent etc). Use the `useOpenPanel` hook instead since these are client tracking only
* Moved: `createNextRouteHandler` is moved to `@openpanel/nextjs/server`
---
## Migration from v1 to v2
URL: https://openpanel.dev/docs/migration/migrate-v1-to-v2
## What's New in v2
* **Redesigned dashboard** - New UI built with Tanstack
* **[Revenue tracking](/features/revenue-tracking)** - Track revenue alongside your analytics
* **Sessions** - View individual user sessions
* **Real-time view** - Live event stream
* **Customizable dashboards** - Grafana-style widget layouts
* **Improved report builder** - Faster and more flexible
* **General improvements** - We have also made a bunch of bug fixes, minor improvements and much more
## Migrating from v1
### Ensure you're on the self-hosting branch
Sometimes we add new helper scripts and what not. Always make sure you're on the latest commit before continuing.
```bash
cd ./self-hosting
git fetch origin
git checkout self-hosting
git pull origin self-hosting
```
### Envs
Since we have migrated to tanstack from nextjs we first need to update our envs. We have added a dedicated page for the [environment variables here](/docs/self-hosting/environment-variables).
```js title=".env"
NEXT_PUBLIC_DASHBOARD_URL="..." // [!code --]
NEXT_PUBLIC_API_URL="..." // [!code --]
NEXT_PUBLIC_SELF_HOSTED="..." // [!code --]
DASHBOARD_URL="..." // [!code ++]
API_URL="..." // [!code ++]
SELF_HOSTED="..." // [!code ++]
```
### Clickhouse 24 -> 25
We have updated Clickhouse to 25, this is important to not skip, otherwise your OpenPanel instance wont work.
You should edit your `./self-hosting/docker-compose.yml`
```js title="./self-hosting/docker-compose.yml"
services:
op-ch:
image: clickhouse/clickhouse-server:24.3.2-alpine // [!code --]
image: clickhouse/clickhouse-server:25.10.2.65 // [!code ++]
```
Since version 25 clickhouse enabled default user setup, this means that we need to disable it to avoid connection issues. With this setting we can still access our clickhouse instance (internally) without having a user.
```
services:
op-ch:
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
```
### Use our latest docker images
Last thing to do is to start using our latest docker images.
> Note: Before you might have been using the latest tag, which is not recommended. Change it to the actual latest version instead.
```js title="./self-hosting/docker-compose.yml"
services:
op-api:
image: lindesvard/openpanel-api:latest // [!code --]
image: lindesvard/openpanel-api:2.0.0 // [!code ++]
op-worker:
image: lindesvard/openpanel-worker:latest // [!code --]
image: lindesvard/openpanel-worker:2.0.0 // [!code ++]
op-dashboard:
image: lindesvard/openpanel-dashboard:latest // [!code --]
image: lindesvard/openpanel-dashboard:2.0.0 // [!code ++]
```
### Done?
When you're done with above steps you should need to restart all services. This will take quite some time depending on your hardware and how many events you have. Since we have made significant changes to the database schema and data we need to run migrations.
```bash
./stop
./start
```
## Using Coolify?
If you're using Coolify and running OpenPanel v1 you'll need to apply the above changes. You can take a look at our [Coolify PR](https://github.com/coollabsio/coolify/pull/7653) which shows what you need to change.
## Any issues with migrations?
If you stumble upon any issues during migrations, please reach out to us on [Discord](https://discord.gg/openpanel) and we'll try our best to help you out.
---
## Revenue tracking
URL: https://openpanel.dev/docs/revenue-tracking
import { FlowStep } from '@/components/flow-step';
[Revenue tracking](/features/revenue-tracking) is a great way to get a better understanding of what your best revenue source is. On this page we'll break down how to get started.
Before we start, we need to know some fundamentals about how OpenPanel and your payment provider work and how we can link a payment to a visitor.
### Payment providers
Usually, you create your checkout from your backend, which then returns a payment link that your visitor will be redirected to. When creating the checkout link, you usually add additional fields such as metadata, customer information, or order details. We'll add the device ID information in this metadata field to be able to link your payment to a visitor.
### OpenPanel
OpenPanel is a cookieless analytics tool that identifies visitors using a `device_id`. To link a payment to a visitor, you need to capture their `device_id` before they complete checkout. This `device_id` will be stored in your payment provider's metadata, and when the payment webhook arrives, you'll use it to associate the revenue with the correct visitor.
## Some typical flows
* [Revenue tracking from your backend (not identified)](#revenue-tracking-from-your-backend-webhook)
* [Revenue tracking from your backend (identified)](#revenue-tracking-from-your-backend-webhook-identified)
* [Revenue tracking from your frontend](#revenue-tracking-from-your-frontend)
* [Revenue tracking without linking it to a identity or device](#revenue-tracking-without-linking-it-to-an-identity-or-device)
### Revenue tracking from your backend (webhook)
This is the most common flow and most secure one. Your backend receives webhooks from your payment provider, and here is the best opportunity to do revenue tracking.
When you create the checkout, you should first call `op.getDeviceId()`, which will return your visitor's current `deviceId`. Pass this to your checkout endpoint.
```javascript
fetch('https://domain.com/api/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
deviceId: op.getDeviceId(), // β since deviceId is here we can link the payment now
// ... other checkout data
}),
})
.then(response => response.json())
.then(data => {
// Handle checkout response, e.g., redirect to payment link
window.location.href = data.paymentUrl;
})
```
```javascript
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST(req: Request) {
const { deviceId, amount, currency } = await req.json();
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: currency,
product_data: { name: 'Product Name' },
unit_amount: amount * 100, // Convert to cents
},
quantity: 1,
},
],
mode: 'payment',
metadata: {
deviceId: deviceId, // β since deviceId is here we can link the payment now
},
success_url: 'https://domain.com/success',
cancel_url: 'https://domain.com/cancel',
});
return Response.json({
paymentUrl: session.url,
});
}
```
```javascript
export async function POST(req: Request) {
const event = await req.json();
// Stripe sends events with type and data.object structure
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const deviceId = session.metadata.deviceId;
const amount = session.amount_total;
op.revenue(amount, { deviceId }); // β since deviceId is here we can link the payment now
}
return Response.json({ received: true });
}
```
***
### Revenue tracking from your backend (webhook) - Identified users
If your visitors are identified (meaning you have called `identify` with a `profileId`), this process gets a bit easier. You don't need to pass the `deviceId` when creating your checkout, and you only need to provide the `profileId` (in backend) to the revenue call.
When a visitor logs in or is identified, call `op.identify()` with their unique `profileId`.
```javascript
op.identify({
profileId: 'user-123', // Unique identifier for this user
email: 'user@example.com',
firstName: 'John',
lastName: 'Doe',
});
```
Since the visitor is already identified, you don't need to fetch or pass the `deviceId`. Just send the checkout data.
```javascript
fetch('https://domain.com/api/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
// β No deviceId needed - user is already identified
// ... other checkout data
}),
})
.then(response => response.json())
.then(data => {
// Handle checkout response, e.g., redirect to payment link
window.location.href = data.paymentUrl;
})
```
Since the user is authenticated, you can get their `profileId` from the session and store it in metadata for easy retrieval in the webhook.
```javascript
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST(req: Request) {
const { amount, currency } = await req.json();
// Get profileId from authenticated session
const profileId = req.session.userId; // or however you get the user ID
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: currency,
product_data: { name: 'Product Name' },
unit_amount: amount * 100, // Convert to cents
},
quantity: 1,
},
],
mode: 'payment',
metadata: {
profileId: profileId, // β Store profileId instead of deviceId
},
success_url: 'https://domain.com/success',
cancel_url: 'https://domain.com/cancel',
});
return Response.json({
paymentUrl: session.url,
});
}
```
In the webhook handler, retrieve the `profileId` from the session metadata.
```javascript
export async function POST(req: Request) {
const event = await req.json();
// Stripe sends events with type and data.object structure
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const profileId = session.metadata.profileId;
const amount = session.amount_total;
op.revenue(amount, { profileId }); // β Use profileId instead of deviceId
}
return Response.json({ received: true });
}
```
***
### Revenue tracking from your frontend
This flow tracks revenue directly from your frontend. Since the success page doesn't have access to the payment amount (payment happens on Stripe's side), we track revenue when checkout is initiated and then confirm it on the success page.
When the visitor clicks the checkout button, track the revenue with the amount.
```javascript
async function handleCheckout() {
const amount = 2000; // Amount in cents
// Create a pending revenue (stored in sessionStorage)
op.pendingRevenue(amount, {
productId: '123',
// ... other properties
});
// Redirect to Stripe checkout
window.location.href = 'https://checkout.stripe.com/...';
}
```
On your success page, flush all pending revenue events. This will send all pending revenues tracked during checkout and clear them from sessionStorage.
```javascript
// Flush all pending revenues
await op.flushRevenue();
// Or if you want to clear without sending (e.g., payment was cancelled)
op.clearRevenue();
```
#### Pros:
* Quick way to get going
* No backend required
* Can track revenue immediately when checkout starts
#### Cons:
* Less accurate (visitor might not complete payment)
* Less "secure" meaning anyone could post revenue data
***
### Revenue tracking without linking it to an identity or device
If you simply want to track revenue totals without linking payments to specific visitors or devices, you can call `op.revenue()` directly from your backend without providing a `deviceId` or `profileId`. This is the simplest approach and works well when you only need aggregate revenue data.
Simply call `op.revenue()` with the amount. No `deviceId` or `profileId` is needed.
```javascript
export async function POST(req: Request) {
const event = await req.json();
// Stripe sends events with type and data.object structure
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const amount = session.amount_total;
op.revenue(amount); // β Simple revenue tracking without linking to a visitor
}
return Response.json({ received: true });
}
```
#### Pros:
* Simplest implementation
* No need to capture or pass device IDs
* Works well for aggregate revenue tracking
#### Cons:
* **You can't dive deeper into where this revenue came from.** For instance, you won't be able to see which source generates the best revenue, which campaigns are most profitable, or which visitors are your highest-value customers.
* Revenue events won't be linked to specific user journeys or sessions
## Available methods
### Revenue
The revenue method will create a revenue event. It's important to know that this method will not work if your OpenPanel instance didn't receive a client secret (for security reasons). You can enable frontend revenue tracking within your project settings.
```javascript
op.revenue(amount: number, properties: Record): Promise
```
### Add a pending revenue
This method will create a pending revenue item and store it in sessionStorage. It will not be sent to OpenPanel until you call `flushRevenue()`. Pending revenues are automatically restored from sessionStorage when the SDK initializes.
```javascript
op.pendingRevenue(amount: number, properties?: Record): void
```
### Send all pending revenues
This method will send all pending revenues to OpenPanel and then clear them from sessionStorage. Returns a Promise that resolves when all revenues have been sent.
```javascript
await op.flushRevenue(): Promise
```
### Clear any pending revenue
This method will clear all pending revenues from memory and sessionStorage without sending them to OpenPanel. Useful if a payment was cancelled or you want to discard pending revenues.
```javascript
op.clearRevenue(): void
```
### Fetch your current users device id
```javascript
op.getDeviceId(): string
```
---
## SDKs Overview
URL: https://openpanel.dev/docs/sdks
import { Callout } from 'fumadocs-ui/components/callout';
OpenPanel provides SDKs for a wide range of platforms and frameworks, making it easy to integrate analytics into your application regardless of your tech stack.
## Quick Start
For most web projects, we recommend starting with one of these:
* **[Script Tag](/docs/sdks/script)** - The quickest way to get started, no build step required
* **[Web SDK](/docs/sdks/web)** - For TypeScript support and more control
* **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js applications | [Setup guide](/guides/nextjs-analytics)
## Web & Browser SDKs
### Simple Integration
* **[Script Tag](/docs/sdks/script)** - Add analytics with a simple `
```
### Accessing via useNuxtApp
You can also access the OpenPanel instance directly via `useNuxtApp()`:
```vue
```
### Tracking Events
You can track events with two different methods: by calling the `op.track()` method directly or by adding `data-track` attributes to your HTML elements.
```vue
```
### Identifying Users
To identify a user, call the `op.identify()` method with a unique identifier.
```vue
```
### Setting Global Properties
To set properties that will be sent with every event:
```vue
```
### Incrementing Properties
To increment a numeric property on a user profile.
* `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
```vue
```
### Decrementing Properties
To decrement a numeric property on a user profile.
* `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
```vue
```
### Clearing User Data
To clear the current user's data:
```vue
```
## Server side
If you want to track server-side events, you should create an instance of our Javascript SDK. Import `OpenPanel` from `@openpanel/sdk`
When using server events it's important that you use a secret to authenticate the request. This is to prevent unauthorized requests since we cannot use cors headers.
You can use the same clientId but you should pass the associated client secret to the SDK.
```typescript
import { OpenPanel } from '@openpanel/sdk';
const opServer = new OpenPanel({
clientId: '{YOUR_CLIENT_ID}',
clientSecret: '{YOUR_CLIENT_SECRET}',
});
opServer.track('my_server_event', { ok: 'β ' });
// Pass `profileId` to track events for a specific user
opServer.track('my_server_event', { profileId: '123', ok: 'β ' });
```
### Serverless & Edge Functions
If you log events in a serverless environment, make sure to await the event call to ensure it completes before the function terminates.
```typescript
import { OpenPanel } from '@openpanel/sdk';
const opServer = new OpenPanel({
clientId: '{YOUR_CLIENT_ID}',
clientSecret: '{YOUR_CLIENT_SECRET}',
});
export default defineEventHandler(async (event) => {
// Await to ensure event is logged before function completes
await opServer.track('my_server_event', { foo: 'bar' });
return { message: 'Event logged!' };
});
```
### Proxy events
With the `proxy` option enabled, you can proxy your events through your server, which ensures all events are tracked since many adblockers block requests to third-party domains.
```typescript title="nuxt.config.ts"
export default defineNuxtConfig({
modules: ['@openpanel/nuxt'],
openpanel: {
clientId: 'your-client-id',
proxy: true, // Enables proxy at /api/openpanel/*
},
});
```
When `proxy: true` is set:
* The module automatically sets `apiUrl` to `/api/openpanel`
* A server handler is registered at `/api/openpanel/**`
* All tracking requests route through your server
This helps bypass adblockers that might block requests to `api.openpanel.dev`.
---
## Python
URL: https://openpanel.dev/docs/sdks/python
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
The OpenPanel Python SDK allows you to track user behavior in your Python applications. This guide provides instructions for installing and using the Python SDK in your project.
Looking for a step-by-step tutorial? Check out the [Python analytics guide](/guides/python-analytics).
## Installation
### Install dependencies
```bash
pip install openpanel
```
### Initialize
Import and initialize the OpenPanel SDK with your credentials:
```python
from openpanel import OpenPanel
op = OpenPanel(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET"
)
```
### Configuration Options
Additional Python-specific options:
* `filter` - A function that will be called before tracking an event. If it returns false the event will not be tracked
* `disabled` - Set to `True` to disable all [event tracking](/features/event-tracking)
* `global_properties` - Dictionary of properties that will be sent with every event
#### Filter Function Example
```python
def my_filter(event):
# Skip events named 'my_event'
return event.get('name') != 'my_event'
op = OpenPanel(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
filter=my_filter
)
```
## Usage
### Tracking Events
To track an event, use the `track` method:
```python
# Track a simple event
op.track("button_clicked")
# Track with properties
op.track("purchase_completed", {
"product_id": "123",
"price": 99.99,
"currency": "USD"
})
# Track for a specific user
op.track("login_successful", {
"method": "google"
}, profile_id="user_123")
```
### Identifying Users
To identify a user, use the `identify` method with the profile ID as the first argument and a dictionary of traits as the second:
```python
op.identify("user123", {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"tier": "premium",
"company": "Acme Inc"
})
```
### Setting Global Properties
To set properties that will be sent with every event:
```python
op.set_global_properties({
"app_version": "1.0.2",
"environment": "production",
"deployment": "us-east-1"
})
```
### Incrementing Properties
To increment a numeric property on a user profile:
```python
op.increment({
"profile_id": "1",
"property": "visits",
"value": 1 # optional, defaults to 1
})
```
### Decrementing Properties
To decrement a numeric property on a user profile:
```python
op.decrement({
"profile_id": "1",
"property": "credits",
"value": 1 # optional, defaults to 1
})
```
### Clearing User Data
To clear the current user's data:
```python
op.clear()
```
## Advanced Usage
### Thread Safety
The OpenPanel SDK is thread-safe. You can safely use a single instance across multiple threads in your application.
### Error Handling
The SDK includes built-in error handling and will not raise exceptions during normal operation. However, you can wrap SDK calls in try-except blocks for additional safety:
```python
try:
op.track("important_event", {"critical": True})
except Exception as e:
logger.error(f"Failed to track event: {e}")
```
### Disabling Tracking
You can temporarily disable all tracking:
```python
# Disable during initialization
op = OpenPanel(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
disabled=True
)
# Or disable after initialization
op.disabled = True
```
---
## React
URL: https://openpanel.dev/docs/sdks/react
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { PersonalDataWarning } from '@/components/personal-data-warning';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
import WebSdkConfig from '@/components/web-sdk-config.mdx';
## Good to know
Keep in mind that all tracking here happens on the client!
For React SPAs, you can use `@openpanel/web` directly - no need for a separate React SDK. Simply create an OpenPanel instance and use it throughout your application.
## Installation
### Step 1: Install
```bash
npm install @openpanel/web
```
### Step 2: Initialize
Create a shared OpenPanel instance in your project:
```ts title="src/openpanel.ts"
import { OpenPanel } from '@openpanel/web';
export const op = new OpenPanel({
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
});
```
#### Options
* `clientId` - Your OpenPanel client ID (required)
* `apiUrl` - The API URL to send events to (default: `https://api.openpanel.dev`)
* `trackScreenViews` - Automatically track screen views (default: `true`)
* `trackOutgoingLinks` - Automatically track outgoing links (default: `true`)
* `trackAttributes` - Automatically track elements with `data-track` attributes (default: `true`)
* `trackHashChanges` - Track hash changes in URL (default: `false`)
* `disabled` - Disable tracking (default: `false`)
### Step 3: Usage
Import and use the instance in your React components:
```tsx
import { op } from '@/openpanel';
function MyComponent() {
const handleClick = () => {
op.track('button_click', { button: 'signup' });
};
return ;
}
```
## Usage
### Tracking Events
You can track events with two different methods: by calling the `op.track()` method directly or by adding `data-track` attributes to your HTML elements.
```tsx
import { op } from '@/openpanel';
function MyComponent() {
useEffect(() => {
op.track('my_event', { foo: 'bar' });
}, []);
return
My Component
;
}
```
### Identifying Users
To identify a user, call the `op.identify()` method with a unique identifier.
```tsx
import { op } from '@/openpanel';
function LoginComponent() {
const handleLogin = (user: User) => {
op.identify({
profileId: user.id, // Required
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
properties: {
tier: 'premium',
},
});
};
return ;
}
```
### Setting Global Properties
To set properties that will be sent with every event:
```tsx
import { op } from '@/openpanel';
function App() {
useEffect(() => {
op.setGlobalProperties({
app_version: '1.0.2',
environment: 'production',
});
}, []);
return
App
;
}
```
### Incrementing Properties
To increment a numeric property on a user profile.
* `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
```tsx
import { op } from '@/openpanel';
function MyComponent() {
const handleAction = () => {
op.increment({
profileId: '1',
property: 'visits',
value: 1, // optional
});
};
return ;
}
```
### Decrementing Properties
To decrement a numeric property on a user profile.
* `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
```tsx
import { op } from '@/openpanel';
function MyComponent() {
const handleAction = () => {
op.decrement({
profileId: '1',
property: 'visits',
value: 1, // optional
});
};
return ;
}
```
### Clearing User Data
To clear the current user's data:
```tsx
import { op } from '@/openpanel';
function LogoutComponent() {
const handleLogout = () => {
op.clear();
// ... logout logic
};
return ;
}
```
### Revenue Tracking
Track revenue events:
```tsx
import { op } from '@/openpanel';
function CheckoutComponent() {
const handlePurchase = async () => {
// Track revenue immediately
await op.revenue(29.99, { currency: 'USD' });
// Or accumulate revenue and flush later
op.pendingRevenue(29.99, { currency: 'USD' });
op.pendingRevenue(19.99, { currency: 'USD' });
await op.flushRevenue(); // Sends both revenue events
// Clear pending revenue
op.clearRevenue();
};
return ;
}
```
### Optional: Create a Hook
If you prefer using a React hook pattern, you can create your own wrapper:
```ts title="src/hooks/useOpenPanel.ts"
import { op } from '@/openpanel';
export function useOpenPanel() {
return op;
}
```
Then use it in your components:
```tsx
import { useOpenPanel } from '@/hooks/useOpenPanel';
function MyComponent() {
const op = useOpenPanel();
useEffect(() => {
op.track('my_event', { foo: 'bar' });
}, []);
return
My Component
;
}
```
---
## React Native
URL: https://openpanel.dev/docs/sdks/react-native
import Link from 'next/link';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { DeviceIdWarning } from '@/components/device-id-warning';
import { PersonalDataWarning } from '@/components/personal-data-warning';
import { Callout } from 'fumadocs-ui/components/callout';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
Looking for a step-by-step tutorial? Check out the [React Native analytics guide](/guides/react-native-analytics).
## Installation
### Install dependencies
We're dependent on `expo-application` for `buildNumber`, `versionNumber` (and `referrer` on android) and `expo-constants` to get the `user-agent`.
npm
pnpm
yarn
bun
```bash
npm install @openpanel/react-native
npx expo install expo-application expo-constants
```
```bash
npm install @openpanel/react-native
pnpm dlx expo install expo-application expo-constants
```
```bash
npm install @openpanel/react-native
yarn dlx expo install expo-application expo-constants
```
```bash
npm install @openpanel/react-native
bun x expo install expo-application expo-constants
```
### Initialize
On native we use a clientSecret to authenticate the app.
```typescript
import { OpenPanel } from '@openpanel/react-native';
const op = new OpenPanel({
clientId: '{YOUR_CLIENT_ID}',
clientSecret: '{YOUR_CLIENT_SECRET}',
});
```
#### Options
## Usage
### Track event
```typescript
op.track('my_event', { foo: 'bar' });
```
### Navigation / Screen views
```typescript
import { usePathname, useSegments } from 'expo-router';
const op = new Openpanel({ /* ... */ })
function RootLayout() {
// ...
const pathname = usePathname()
// Segments is optional but can be nice to have if you
// want to group routes together
// pathname = /posts/123
// segements = ['posts', '[id]']
const segments = useSegments()
useEffect(() => {
// Simple
op.screenView(pathname)
// With extra data
op.screenView(pathname, {
// segments is optional but nice to have
segments: segments.join('/'),
// other optional data you want to send with the screen view
})
}, [pathname,segments])
// ...
}
```
```tsx
import { createNavigationContainerRef } from '@react-navigation/native'
import { Openpanel } from '@openpanel/react-native'
const op = new Openpanel({ /* ... */ })
const navigationRef = createNavigationContainerRef()
export function NavigationRoot() {
const handleNavigationStateChange = () => {
const current = navigationRef.getCurrentRoute()
if (current) {
op.screenView(current.name, {
params: current.params,
})
}
}
return (
)
}
```
For more information on how to use the SDK, check out the [Javascript SDK](/docs/sdks/javascript#usage).
---
## Remix
URL: https://openpanel.dev/docs/sdks/remix
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated remix sdk soon.
---
## Ruby
URL: https://openpanel.dev/docs/sdks/ruby
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
The OpenPanel Ruby SDK allows you to track user behavior in your Ruby applications. This guide provides instructions for installing and using the Ruby SDK in your project.
View the [Ruby SDK on GitHub](https://github.com/tstaetter/openpanel-ruby-sdk) for the latest updates and source code.
## Installation
### Install dependencies
If you're using Bundler, add to your `Gemfile`:
```bash
bundle add openpanel-sdk
```
Or install the gem directly:
```bash
gem install openpanel-sdk
```
### Set environment variables
Set your environment variables in a `.env` file:
```bash
OPENPANEL_TRACK_URL=https://api.openpanel.dev/track
OPENPANEL_CLIENT_ID=
OPENPANEL_CLIENT_SECRET=
```
### Initialize
Require and initialize the OpenPanel SDK:
```ruby
require 'openpanel-sdk'
tracker = OpenPanel::SDK::Tracker.new
```
### Configuration Options
Additional Ruby-specific options:
* `disabled` - Set to `true` to disable all [event tracking](/features/event-tracking)
* `env` - Environment name (e.g., `Rails.env.to_s`)
```ruby
tracker = OpenPanel::SDK::Tracker.new(
{ env: Rails.env.to_s },
disabled: Rails.env.development?
)
```
## Usage
### Tracking Events
To track an event, use the `track` method:
```ruby
tracker.track('test_event', payload: { name: 'test' })
```
### Identifying Users
Create an `IdentifyUser` object and pass it to the `identify` method:
```ruby
identify_user = OpenPanel::SDK::IdentifyUser.new
identify_user.profile_id = 'user_123'
identify_user.email = 'user@example.com'
identify_user.first_name = 'John'
identify_user.last_name = 'Doe'
identify_user.properties = { tier: 'premium', company: 'Acme Inc' }
response = tracker.identify(identify_user)
```
### Incrementing Properties
To increment a numeric property on a user profile:
```ruby
tracker.increment_property(identify_user, 'visits', 1)
```
### Decrementing Properties
To decrement a numeric property on a user profile:
```ruby
tracker.decrement_property(identify_user, 'credits', 1)
```
### Filtering Events
Filters are used to prevent sending events to OpenPanel in certain cases. You can filter events by passing a `filter` lambda to the `track` method:
```ruby
filter = lambda { |payload|
# Return true to send the event, false to skip it
payload[:name] == 'test'
}
response = tracker.track('test_event', payload: { name: 'test' }, filter: filter)
# If filter returns false, response will be nil
```
## Rails Integration
### Setting up the Tracker
Add the following to your `application_controller.rb`:
```ruby
before_action :set_openpanel_tracker
protected
def set_openpanel_tracker
@openpanel_tracker = OpenPanel::SDK::Tracker.new(
{ env: Rails.env.to_s },
disabled: Rails.env.development?
)
@openpanel_tracker.set_header 'x-client-ip', request.ip
@openpanel_tracker.set_header 'user-agent', request.user_agent
end
```
### Tracking Events in Controllers
Use `@openpanel_tracker` in your controllers to track events:
```ruby
def create
@user = User.create(user_params)
@openpanel_tracker.track('user_created', payload: { user_id: @user.id })
redirect_to @user
end
```
### Identifying Users
Create a helper method to convert your app's user model to an `IdentifyUser`:
```ruby
def identify_user_from_app_user(user, properties: {})
iu = OpenPanel::SDK::IdentifyUser.new
iu.profile_id = user.id.to_s
iu.email = user.email
iu.first_name = user.first_name
iu.last_name = user.last_name
iu.properties = properties
iu
end
# Usage in controller
def show
iu = identify_user_from_app_user(current_user)
@openpanel_tracker.identify(iu)
end
```
## Advanced Usage
### Setting Custom Headers
You can set custom headers for requests:
```ruby
tracker.set_header 'x-client-ip', request.ip
tracker.set_header 'user-agent', request.user_agent
```
### Error Handling
The SDK returns a `Faraday::Response` object. Check the response status:
```ruby
response = tracker.track('event', payload: { name: 'test' })
if response&.status == 200
puts 'Event tracked successfully'
else
puts "Failed to track event: #{response&.status}"
end
```
### Disabling Tracking
You can disable tracking during initialization or in specific environments:
```ruby
# Disable during initialization
tracker = OpenPanel::SDK::Tracker.new({}, disabled: true)
# Or disable in development
tracker = OpenPanel::SDK::Tracker.new(
{ env: Rails.env.to_s },
disabled: Rails.env.development?
)
```
---
## Rust
URL: https://openpanel.dev/docs/sdks/rust
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
The OpenPanel Rust SDK allows you to track user behavior in your Rust applications. This guide provides instructions for installing and using the Rust SDK in your project.
View the [Rust SDK on GitHub](https://github.com/tstaetter/openpanel-rust-sdk/) for the latest updates and source code.
## Installation
### Install dependencies
Add the following to your `Cargo.toml`:
```toml
[dependencies]
openpanel-sdk = "0.1.0"
```
Or install via cargo:
```bash
cargo add openpanel-sdk
```
### Set environment variables
Set your environment variables in a `.env` file:
```bash
OPENPANEL_TRACK_URL=https://api.openpanel.dev/track
OPENPANEL_CLIENT_ID=
OPENPANEL_CLIENT_SECRET=
```
### Initialize
Import and initialize the OpenPanel SDK:
```rust
use openpanel_sdk::sdk::Tracker;
let tracker = Tracker::try_new_from_env()?.with_default_headers()?;
```
### Configuration Options
## Usage
### Tracking Events
To track an event, use the `track` method:
```rust
use std::collections::HashMap;
use openpanel_sdk::sdk::Tracker;
let mut properties = HashMap::new();
properties.insert("name".to_string(), "rust".to_string());
let response = tracker
.track("test_event".to_string(), Some(properties), None)
.await?;
```
### Identifying Users
To identify a user, you need to convert your user struct into `user::IdentifyUser` by implementing the `From` trait:
```rust
use std::collections::HashMap;
use openpanel_sdk::sdk::Tracker;
use openpanel_sdk::user;
struct Address {
pub street: String,
pub city: String,
pub zip: String,
}
struct AppUser {
pub id: String,
pub email: String,
pub first_name: String,
pub last_name: String,
pub address: Address,
}
impl From for HashMap {
fn from(address: Address) -> Self {
let mut properties = HashMap::new();
properties.insert("street".to_string(), address.street);
properties.insert("city".to_string(), address.city);
properties.insert("zip".to_string(), address.zip);
properties
}
}
impl From for user::IdentifyUser {
fn from(app_user: AppUser) -> Self {
Self {
profile_id: app_user.id,
email: app_user.email,
first_name: app_user.first_name,
last_name: app_user.last_name,
properties: app_user.address.into(),
}
}
}
// Usage
let user = AppUser { /* ... */ };
let response = tracker.identify(user.into()).await?;
```
### Incrementing Properties
To increment a numeric property on a user profile:
```rust
let response = tracker
.increment_property(profile_id, "visits", 1)
.await?;
```
### Decrementing Properties
To decrement a numeric property on a user profile:
```rust
let response = tracker
.decrement_property(profile_id, "credits", 1)
.await?;
```
### Filtering Events
Filters are used to prevent sending events to OpenPanel in certain cases. You can filter events by passing a `filter` function to the `track` method:
```rust
use std::collections::HashMap;
let filter = |properties: HashMap| {
// Return true to send the event, false to skip it
properties.contains_key("required_key")
};
let mut properties = HashMap::new();
properties.insert("name".to_string(), "rust".to_string());
let response = tracker
.track("test_event".to_string(), Some(properties), Some(&filter))
.await;
// If filter returns false, the event won't be sent and an Err is returned
match response {
Ok(_) => println!("Event sent successfully"),
Err(_) => println!("Event was filtered out"),
}
```
## Advanced Usage
### Error Handling
The SDK uses Rust's `Result` type for error handling. Always handle errors appropriately:
```rust
match tracker.track("event".to_string(), Some(properties), None).await {
Ok(response) => {
if response.status() == 200 {
println!("Event tracked successfully");
}
}
Err(e) => {
eprintln!("Failed to track event: {}", e);
}
}
```
### Async Runtime
The SDK uses async/await. Make sure you're running within an async runtime (e.g., Tokio):
```rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let tracker = Tracker::try_new_from_env()?.with_default_headers()?;
// ... use tracker
Ok(())
}
```
---
## Script Tag
URL: https://openpanel.dev/docs/sdks/script
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { PersonalDataWarning } from '@/components/personal-data-warning';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
import WebSdkConfig from '@/components/web-sdk-config.mdx';
## Installation
Just insert this snippet and replace `YOUR_CLIENT_ID` with your client id.
```html title="index.html" /clientId: 'YOUR_CLIENT_ID'/
```
#### Options
## Usage
### Tracking Events
You can track events with two different methods: by calling the `window.op('track')` directly or by adding `data-track` attributes to your HTML elements.
```html title="index.html"
```
```html title="index.html"
```
### Identifying Users
To identify a user, call the `window.op('identify')` method with a unique identifier.
```js title="main.js"
window.op('identify', {
profileId: '123', // Required
firstName: 'Joe',
lastName: 'Doe',
email: 'joe@doe.com',
properties: {
tier: 'premium',
},
});
```
### Setting Global Properties
To set properties that will be sent with every event:
```js title="main.js"
window.op('setGlobalProperties', {
app_version: '1.0.2',
environment: 'production',
});
```
### Incrementing Properties
To increment a numeric property on a user profile.
* `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
```js title="main.js"
window.op('increment', {
profileId: '1',
property: 'visits',
value: 1 // optional
});
```
### Decrementing Properties
To decrement a numeric property on a user profile.
* `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
```js title="main.js"
window.op('decrement', {
profileId: '1',
property: 'visits',
value: 1 // optional
});
```
### Clearing User Data
To clear the current user's data:
```js title="main.js"
window.op('clear');
```
## Advanced Usage
### Filtering events
You can filter out events by adding a `filter` property to the `init` method.
Below is an example of how to disable tracking for users who have a `disable_tracking` item in their local storage.
```js title="main.js"
window.op('init', {
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
filter: () => localStorage.getItem('disable_tracking') === undefined,
});
```
### Using the Web SDK with NPM
#### Step 1: Install the SDK
npm
pnpm
yarn
bun
```bash
npm install @openpanel/web
```
```bash
pnpm add @openpanel/web
```
```bash
yarn add @openpanel/web
```
```bash
bun add @openpanel/web
```
#### Step 2: Initialize the SDK
```js title="op.js"
import { OpenPanel } from '@openpanel/web';
const op = new OpenPanel({
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
});
```
#### Step 3: Use the SDK
```js title="main.js"
import { op } from './op.js';
op.track('my_event', { foo: 'bar' });
```
### Typescript
Getting ts errors when using the SDK? You can add a custom type definition file to your project.
#### Simple
Just paste this code in any of your `.d.ts` files.
```ts title="op.d.ts"
declare global {
interface Window {
op: {
q?: string[][];
(...args: [
'init' | 'track' | 'identify' | 'setGlobalProperties' | 'increment' | 'decrement' | 'clear',
...any[]
]): void;
};
}
}
```
#### Strict typing (from sdk)
##### Step 1: Install the SDK
npm
pnpm
yarn
bun
```bash
npm install @openpanel/web
```
```bash
pnpm add @openpanel/web
```
```bash
yarn add @openpanel/web
```
```bash
bun add @openpanel/web
```
##### Step 2: Create a type definition file
Create a `op.d.ts`file and paste the following code:
```ts title="op.d.ts"
///
```
---
## Swift
URL: https://openpanel.dev/docs/sdks/swift
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
The OpenPanel Swift SDK allows you to integrate OpenPanel analytics into your iOS, macOS, tvOS, and watchOS applications.
Looking for a step-by-step tutorial? Check out the [Swift analytics guide](/guides/swift-analytics).
## Features
* Easy-to-use API for tracking events and user properties
* Automatic collection of app states
* Support for custom event properties
* Shared instance for easy access throughout your app
## Requirements
* iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
* Xcode 12.0+
* Swift 5.3+
## Installation
### Step 1: Add Package via Swift Package Manager
You can add OpenPanel to an Xcode project by adding it as a package dependency.
1. From the **File** menu, select **Add Packages...**
2. Enter `https://github.com/Openpanel-dev/swift-sdk` into the package repository URL text field
3. Click **Add Package**
Alternatively, if you have a `Package.swift` file, you can add OpenPanel as a dependency:
```swift
dependencies: [
.package(url: "https://github.com/Openpanel-dev/swift-sdk")
]
```
### Step 2: Import and Initialize
First, import the SDK in your Swift file:
```swift
import OpenPanel
```
Then, initialize the OpenPanel SDK with your client ID:
```swift
OpenPanel.initialize(options: .init(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET"
))
```
### Configuration Options
Additional Swift-specific options:
* `filter` - A closure that will be called before tracking an event. If it returns false, the event will not be tracked
* `disabled` - Set to `true` to disable all [event tracking](/features/event-tracking)
* `automaticTracking` - Set to `true` to automatically track app lifecycle events
#### Filter Example
```swift
OpenPanel.initialize(options: .init(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
filter: { payload in
// Your custom filtering logic here
return true // or false to filter out the event
}
))
```
## Usage
### Tracking Events
To track an event:
```swift
OpenPanel.track(name: "Button Clicked", properties: ["button_id": "submit_form"])
```
### Identifying Users
To identify a user:
```swift
OpenPanel.identify(payload: IdentifyPayload(
profileId: "user123",
firstName: "John",
lastName: "Doe",
email: "john@example.com",
properties: ["subscription": "premium"]
))
```
### Setting Global Properties
To set properties that will be sent with every event:
```swift
OpenPanel.setGlobalProperties([
"app_version": "1.0.2",
"environment": "production"
])
```
### Incrementing Properties
To increment a numeric property:
```swift
OpenPanel.increment(payload: IncrementPayload(profileId: "user123", property: "login_count"))
```
### Decrementing Properties
To decrement a numeric property:
```swift
OpenPanel.decrement(payload: DecrementPayload(profileId: "user123", property: "credits_remaining"))
```
## Advanced Usage
### Disabling Tracking
You can temporarily disable tracking during initialization:
```swift
OpenPanel.initialize(options: .init(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
disabled: true
))
```
### Custom Event Filtering
You can set up custom event filtering during initialization:
```swift
OpenPanel.initialize(options: .init(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
filter: { payload in
// Your custom filtering logic here
return true // or false to filter out the event
}
))
```
### Automatic Tracking
The SDK automatically tracks app lifecycle events (`app_opened` and `app_closed`) if `automaticTracking` is set to `true` during initialization:
```swift
OpenPanel.initialize(options: .init(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
automaticTracking: true
))
```
## Thread Safety
The OpenPanel SDK is designed to be thread-safe. You can call its methods from any thread without additional synchronization.
---
## Vue
URL: https://openpanel.dev/docs/sdks/vue
import Link from 'next/link';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { DeviceIdWarning } from '@/components/device-id-warning';
import { PersonalDataWarning } from '@/components/personal-data-warning';
import { Callout } from 'fumadocs-ui/components/callout';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
import WebSdkConfig from '@/components/web-sdk-config.mdx';
Looking for a step-by-step tutorial? Check out the [Vue analytics guide](/guides/vue-analytics).
## Good to know
Keep in mind that all tracking here happens on the client!
For Vue SPAs, you can use `@openpanel/web` directly - no need for a separate Vue SDK. Simply create an OpenPanel instance and use it throughout your application.
## Installation
### Step 1: Install
```bash
pnpm install @openpanel/web
```
### Step 2: Initialize
Create a shared OpenPanel instance in your project:
```ts title="src/openpanel.ts"
import { OpenPanel } from '@openpanel/web';
export const op = new OpenPanel({
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
});
```
#### Options
* `clientId` - Your OpenPanel client ID (required)
* `apiUrl` - The API URL to send events to (default: `https://api.openpanel.dev`)
* `trackScreenViews` - Automatically track screen views (default: `true`)
* `trackOutgoingLinks` - Automatically track outgoing links (default: `true`)
* `trackAttributes` - Automatically track elements with `data-track` attributes (default: `true`)
* `trackHashChanges` - Track hash changes in URL (default: `false`)
* `disabled` - Disable tracking (default: `false`)
### Step 3: Usage
Import and use the instance in your Vue components:
```vue
```
## Usage
### Tracking Events
You can track events with two different methods: by calling the `op.track()` method directly or by adding `data-track` attributes to your HTML elements.
```vue
```
### Identifying Users
To identify a user, call the `op.identify()` method with a unique identifier.
```vue
```
### Setting Global Properties
To set properties that will be sent with every event:
```vue
```
### Incrementing Properties
To increment a numeric property on a user profile.
* `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
```vue
```
### Decrementing Properties
To decrement a numeric property on a user profile.
* `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
```vue
```
### Clearing User Data
To clear the current user's data:
```vue
```
### Revenue Tracking
Track revenue events:
```vue
```
### Optional: Create a Composable
If you prefer using a composable pattern, you can create your own wrapper:
```ts title="src/composables/useOpenPanel.ts"
import { op } from '@/openpanel';
export function useOpenPanel() {
return op;
}
```
Then use it in your components:
```vue
```
---
## Javascript (Web)
URL: https://openpanel.dev/docs/sdks/web
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { PersonalDataWarning } from '@/components/personal-data-warning';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
import WebSdkConfig from '@/components/web-sdk-config.mdx';
## Installation
### Step 1: Install
npm
pnpm
yarn
bun
```bash
npm install @openpanel/web
```
```bash
pnpm add @openpanel/web
```
```bash
yarn add @openpanel/web
```
```bash
bun add @openpanel/web
```
### Step 2: Initialize
```js title="op.ts"
import { OpenPanel } from '@openpanel/web';
const op = new OpenPanel({
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
});
```
#### Options
### Step 3: Usage
```js title="main.ts"
import { op } from './op.js';
op.track('my_event', { foo: 'bar' });
```
## Usage
Refer to the [Javascript SDK](/docs/sdks/javascript#usage) for usage instructions.
---
## Changelog for self-hosting
URL: https://openpanel.dev/docs/self-hosting/changelog
## 2.0.0
We have released the first stable version of OpenPanel v2. This is a big one!
Read more about it in our [migration guide](/docs/migration/migrate-v1-to-v2).
TLDR;
* Clickhouse upgraded from 24.3.2-alpine to 25.10.2.65
* Add `CLICKHOUSE_SKIP_USER_SETUP=1` to op-ch service
* `NEXT_PUBLIC_DASHBOARD_URL` -> `DASHBOARD_URL`
* `NEXT_PUBLIC_API_URL` -> `API_URL`
* `NEXT_PUBLIC_SELF_HOSTED` -> `SELF_HOSTED`
## 1.2.0
We have renamed `SELF_HOSTED` to `NEXT_PUBLIC_SELF_HOSTED`. It's important to rename this env before your upgrade to this version.
## 1.1.1
Packed with new features since our first stable release.
## 1.0.0 (stable)
OpenPanel self-hosting is now in a stable state and should not be any breaking changes in the future.
If you are upgrading from a previous version, you should keep an eye on the logs since it well tell you if you need to take any actions. Its not mandatory but its recommended since it might bite you in the \*ss later.
### New environment variables.
If you upgrading from a previous version, you'll need to edit your `.env` file if you want to use these new variables.
* `ALLOW_REGISTRATION` - If set to `false` new users will not be able to register (only the first user can register).
* `ALLOW_INVITATION` - If set to `false` new users will not be able to be invited.
* `RESEND_API_KEY` - If set, we'll use Resend to send e-mails.
* `EMAIL_SENDER` - The e-mail address that will be used to send e-mails.
### Removed Clickhouse Keeper
In 0.0.6 we introduced a cluster mode for Clickhouse. This was a mistake and we have removed it.
Remove op-zk from services and volumes
```
services:
op-zk:
image: clickhouse/clickhouse-server:24.3.2-alpine
volumes:
- op-zk-data:/var/lib/clickhouse
- ./clickhouse/clickhouse-keeper-config.xml:/etc/clickhouse-server/config.xml
command: [ 'clickhouse-keeper', '--config-file', '/etc/clickhouse-server/config.xml' ]
restart: always
ulimits:
nofile:
soft: 262144
hard: 262144
volumes:
op-zk-data:
driver: local
```
## 0.0.6
Removed Clerk.com and added self-hosted authentication. For more info read our [migrating from clerk](/docs/self-hosting/migrating-from-clerk)
---
## Deploy with Coolify
URL: https://openpanel.dev/docs/self-hosting/deploy-coolify
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
[Coolify](https://coolify.io) is an open-source, self-hosted platform that simplifies deploying applications. OpenPanel is available as a one-click service in Coolify, making deployment quick and easy.
## Prerequisites
* A Coolify instance installed and running
* A server with at least 2GB RAM (4GB+ recommended)
* Domain name configured in Coolify (optional but recommended)
## Quick Start
### Create a New Resource
1. Log in to your Coolify dashboard
2. Navigate to your project
3. Click **"New Resource"** or **"Add Service"**
4. Select **"One-Click Services"** or **"Docker Compose"**
### Select OpenPanel
1. Search for **"OpenPanel"** in the services list
2. Click on **OpenPanel** to select it
3. The service template will be automatically filled in with the required configuration
### Configure Your Deployment
Coolify will automatically configure most settings, but you may want to customize:
* **Domain**: Set your domain name for the dashboard
* **Environment Variables**: Configure optional settings like:
* `ALLOW_REGISTRATION`: Set to `false` to disable public registration
* `ALLOW_INVITATION`: Set to `true` to allow user invitations
* `RESEND_API_KEY`: Your Resend API key for email features
* `EMAIL_SENDER`: Email sender address
* `OPENAI_API_KEY`: OpenAI API key for AI features (optional)
* `AI_MODEL`: AI model to use (`gpt-4o-mini`, `gpt-4o`, or `claude-3-5`)
Coolify automatically handles:
* Database setup (PostgreSQL)
* Redis configuration
* ClickHouse setup
* SSL certificates
* Service health checks
* Automatic restarts
### Deploy
1. Review your configuration
2. Click **"Deploy"** or **"Save"**
3. Coolify will automatically:
* Pull the required Docker images
* Start all services
* Run database migrations
* Set up SSL certificates (if domain is configured)
Wait for all services to become healthy. You can monitor the deployment progress in the Coolify dashboard.
### Access Your Dashboard
Once deployment is complete, you can access OpenPanel at your configured domain. The first user to register will become the admin account.
By default, registration is disabled after the first user is created. Make sure to register your admin account first!
## Service Structure
Coolify deploys OpenPanel with the following services:
* **opapi**: OpenPanel API server (handles `/api` routes)
* **opdashboard**: OpenPanel dashboard (frontend)
* **opworker**: Background worker for processing events
* **opdb**: PostgreSQL database
* **opkv**: Redis cache
* **opch**: ClickHouse analytics database
## Configuration
### Environment Variables
You can configure OpenPanel through environment variables in Coolify. Coolify automatically sets the required database and connection variables.
For a complete reference of all available environment variables, see the [Environment Variables documentation](/docs/self-hosting/environment-variables).
#### Coolify-Specific Notes
Coolify automatically handles these variables:
* `DATABASE_URL`: PostgreSQL connection string
* `REDIS_URL`: Redis connection string
* `CLICKHOUSE_URL`: ClickHouse connection string
* `API_URL`: API endpoint URL (set via `SERVICE_FQDN_OPAPI`)
* `DASHBOARD_URL`: Dashboard URL (set via `SERVICE_FQDN_OPDASHBOARD`)
* `COOKIE_SECRET`: Automatically generated secret
You can configure optional variables like `ALLOW_REGISTRATION`, `RESEND_API_KEY`, `OPENAI_API_KEY`, etc. through Coolify's environment variable interface.
### Updating OpenPanel
To update OpenPanel in Coolify:
1. Navigate to your OpenPanel service
2. Click **"Redeploy"** or **"Update"**
3. Coolify will pull the latest images and restart services
Database migrations run automatically when the API service starts, so updates are seamless.
### Scaling
You can scale the worker service in Coolify:
1. Navigate to your OpenPanel service
2. Edit the `opworker` service configuration
3. Adjust the replica count
4. Save and redeploy
## Troubleshooting
### Services Not Starting
1. Check service logs in Coolify dashboard
2. Verify all environment variables are set correctly
3. Ensure your server has enough resources (RAM, disk space)
### Database Connection Issues
1. Verify the database service (`opdb`) is running
2. Check that `DATABASE_URL` is correctly formatted
3. Review database logs in Coolify
### SSL Certificate Issues
If SSL certificates aren't being issued:
1. Verify your domain DNS is pointing to Coolify
2. Check Coolify's SSL/TLS settings
3. Review Coolify logs for Let's Encrypt errors
### Health Check Failures
If health checks are failing:
1. Check service logs for errors
2. Verify all dependencies are running
3. Increase health check timeout if needed
## Using Your Own Database
If you want to use an external PostgreSQL database:
1. Create a new PostgreSQL database in Coolify or use an external service
2. Update the `DATABASE_URL` environment variable in your OpenPanel service
3. Update `DATABASE_URL_DIRECT` to match
4. Redeploy the service
The same applies to Redis and ClickHouse if you want to use external services.
## Backup and Restore
### Backup
Coolify provides built-in backup functionality:
1. Navigate to your database service (`opdb`)
2. Configure backup settings
3. Set up backup schedule
4. Backups will be stored according to your Coolify configuration
### Manual Backup
You can also create manual backups:
1. Use Coolify's terminal access
2. Export the database:
```bash
docker exec opdb pg_dump -U postgres openpanel-db > backup.sql
```
### Restore
To restore from a backup:
1. Use Coolify's terminal access
2. Restore the database:
```bash
docker exec -i opdb psql -U postgres openpanel-db < backup.sql
```
## Next Steps
* [Configure email settings](/docs/self-hosting/self-hosting#e-mail) for password resets and invitations
* [Set up AI integration](/docs/self-hosting/self-hosting#ai-integration) for the analytics assistant
* [Configure SDK](/docs/self-hosting/self-hosting#always-use-correct-api-url) to track events from your applications
## Additional Resources
* [Coolify Documentation](https://coolify.io/docs)
* [Coolify Services Directory](https://coolify.io/docs/services)
* [OpenPanel on Coolify](https://coolify.io/docs/services/openpanel)
---
## Deploy with Docker Compose
URL: https://openpanel.dev/docs/self-hosting/deploy-docker-compose
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
This guide will help you deploy OpenPanel using Docker Compose. This method gives you full control over your deployment and is perfect for self-hosting on a VPS or dedicated server.
## Prerequisites
* A VPS or server (Docker and Node will be installed automatically if needed)
* At least 2GB RAM (4GB+ recommended)
* Domain name pointing to your server (optional but recommended)
* Basic knowledge of command line
πββοΈ This should work on any system. The setup script will install Docker and Node if they're not already installed.
## Quick Start
### Clone the Repository
Clone the OpenPanel repository and navigate to the self-hosting directory:
```bash
git clone -b self-hosting https://github.com/Openpanel-dev/openpanel.git
cd openpanel/self-hosting
```
### Run the Setup Script
The setup script will guide you through the configuration process. It will:
1. Install Node.js (if you accept and it's not already installed)
2. Install Docker (if you accept and it's not already installed)
3. Run an interactive quiz/wizard that asks questions about your setup
> Setup takes 30s to 2 minutes depending on your VPS
```bash
./setup
```
The wizard will ask you questions about:
* Your domain name
* Database configuration
* Email settings (optional)
* AI integration (optional)
* Registration settings
β οΈ If the `./setup` script fails to run, you can do it manually:
1. Install Docker
2. Install Node.js
3. Install npm
4. Run `npm run quiz` inside the self-hosting folder
### Start the Services
After the setup is complete, start all OpenPanel services:
```bash
./start
```
This will start all required services:
* **op-db**: PostgreSQL database
* **op-kv**: Redis cache
* **op-ch**: ClickHouse analytics database
* **op-api**: OpenPanel API server
* **op-dashboard**: OpenPanel dashboard (frontend)
* **op-worker**: Background worker for processing events
### Verify Installation
Check that all containers are running:
```bash
docker compose ps
```
All services should show as "healthy" or "running". You can also check the logs:
```bash
docker compose logs -f
```
Or use the provided logs script:
```bash
./logs
```
Once all services are healthy, you can access OpenPanel at your configured domain (or `http://your-server-ip` if you haven't configured a domain).
## Configuration
### Environment Variables
The setup wizard will configure most environment variables automatically. You can manually edit the `.env` file in the `self-hosting` directory if needed.
For a complete reference of all available environment variables, see the [Environment Variables documentation](/docs/self-hosting/environment-variables).
If you change the `.env` file, you need to restart the services for the changes to take effect. Use `./stop` and `./start` or `docker compose restart`.
### Using Custom Docker Images
If you want to use specific image versions, edit the `docker-compose.yml` file and update the image tags:
```yaml
op-api:
image: lindesvard/openpanel-api:2.0.0 # Specify version
```
### Scaling Workers
To scale the worker service, set the `OP_WORKER_REPLICAS` environment variable:
```bash
OP_WORKER_REPLICAS=3 docker compose up -d
```
Or edit the `docker-compose.yml` file:
```yaml
op-worker:
deploy:
replicas: 3
```
## Managing Your Deployment
OpenPanel comes with several utility scripts to help manage your deployment. All scripts should be run from within the `self-hosting` directory.
### Basic Operations
```bash
./start # Start all OpenPanel services
./stop # Stop all OpenPanel services
./logs # View real-time logs from all services
```
### View Logs
View logs from all services:
```bash
./logs
```
Or using Docker Compose directly:
```bash
docker compose logs -f
```
View logs from a specific service:
```bash
docker compose logs -f op-api
```
### Stop Services
Stop all services:
```bash
./stop
```
Or using Docker Compose:
```bash
docker compose down
```
Stop services but keep volumes (data persists):
```bash
docker compose stop
```
### Restart Services
Restart all services:
```bash
./stop && ./start
```
Or using Docker Compose:
```bash
docker compose restart
```
Restart a specific service:
```bash
docker compose restart op-api
```
### Rebuild Services
Rebuild and restart a specific service:
```bash
./rebuild op-dashboard
```
### Update OpenPanel
To update to the latest version, use the update script:
```bash
./update
```
This script will:
1. Pull the latest changes from the repository
2. Pull the latest Docker images
3. Restart all services
If you don't have the `./update` script, you can manually update:
```bash
git pull
docker compose pull
docker compose up -d
```
Always backup your data before updating. The database migrations will run automatically when the API container starts. Also read any changes in the [changelog](/docs/self-hosting/changelog) and apply them to your instance.
### Backup and Restore
#### Backup
Backup your PostgreSQL database:
```bash
docker compose exec op-db pg_dump -U postgres postgres > backup.sql
```
Backup volumes:
```bash
docker run --rm -v openpanel_op-db-data:/data -v $(pwd):/backup alpine tar czf /backup/db-backup.tar.gz /data
```
#### Restore
Restore PostgreSQL database:
```bash
docker compose exec -T op-db psql -U postgres postgres < backup.sql
```
## Troubleshooting
### Services Won't Start
1. Check Docker and Docker Compose versions:
```bash
docker --version
docker compose version
```
2. Check available disk space:
```bash
df -h
```
3. Check logs for errors:
```bash
docker compose logs
```
### Database Connection Issues
If services can't connect to the database:
1. Verify the database is healthy:
```bash
docker compose ps op-db
```
2. Check database logs:
```bash
docker compose logs op-db
```
3. Verify `DATABASE_URL` in your `.env` file matches the service name `op-db`
### Port Conflicts
If ports 80 or 443 are already in use, you can:
1. Change the ports in `docker-compose.yml`:
```yaml
ports:
- "8080:80"
- "8443:443"
```
2. Or stop the conflicting service
### Health Check Failures
If health checks are failing:
1. Check if services are actually running:
```bash
docker compose ps
```
2. Increase health check timeout in `docker-compose.yml`:
```yaml
healthcheck:
interval: 30s
timeout: 10s
retries: 10
```
## Using Your Own Reverse Proxy
If you're using your own reverse proxy (like Nginx or Traefik), you can disable the included Caddy proxy by commenting it out in `docker-compose.yml`:
```yaml
# op-proxy:
# image: caddy:2-alpine
# ...
```
Then configure your reverse proxy to forward requests:
* `/api/*` β `op-api:3000`
* `/*` β `op-dashboard:3000`
## Next Steps
* [Configure email settings](/docs/self-hosting/self-hosting#e-mail) for password resets and invitations
* [Set up AI integration](/docs/self-hosting/self-hosting#ai-integration) for the analytics assistant
* [Configure SDK](/docs/self-hosting/self-hosting#always-use-correct-api-url) to track events from your applications
---
## Deploy with Dokploy
URL: https://openpanel.dev/docs/self-hosting/deploy-dokploy
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
[Dokploy](https://dokploy.com) is an open-source, self-hosted platform for deploying applications. OpenPanel can be deployed on Dokploy using the Docker Compose template, with some specific configuration requirements.
β οΈ **Important**: The Dokploy template requires specific configuration that differs from Coolify. Make sure to follow all steps carefully, especially the environment variables and domain configuration.
β οΈ **Important**: We have an open issue on dokploy [https://github.com/Dokploy/templates/issues/292](https://github.com/Dokploy/templates/issues/292) and hoping it will be resolved.
## Prerequisites
* A Dokploy instance installed and running
* A server with at least 2GB RAM (4GB+ recommended)
* Domain name configured in Dokploy
## Quick Start
### Deploy OpenPanel Template
1. Log in to your Dokploy dashboard
2. Navigate to your project
3. Click **"New Application"** or **"Deploy"**
4. Select **"Docker Compose"** or search for **"OpenPanel"** template
5. Select the OpenPanel template
### Configure Domain Names
Configure your domain names in Dokploy:
1. Set up the main domain for the dashboard (e.g., `analytics.yourdomain.com`)
2. Configure the API domain - this should be the **same domain** as the dashboard, with the API path forwarded to `/api`
The API and dashboard use the same domain. The API service has a forward path to `/api`, so make sure to configure this correctly.
### Configure Environment Variables
Edit the `.env` file or environment variables in Dokploy. You **must** set these environment variables:
```bash
# Required: Set these to your actual domain
API_URL=https://yourdomain.com/api
DASHBOARD_URL=https://yourdomain.com
# Database Configuration (automatically set by Dokploy)
OPENPANEL_POSTGRES_DB=openpanel-db
SERVICE_USER_POSTGRES=postgres
SERVICE_PASSWORD_POSTGRES=
SERVICE_PASSWORD_REDIS=
# Optional Configuration
OPENPANEL_ALLOW_REGISTRATION=false
OPENPANEL_ALLOW_INVITATION=true
RESEND_API_KEY=your-resend-api-key
OPENPANEL_EMAIL_SENDER=noreply@yourdomain.com
```
β οΈ **Critical**: Unlike Coolify, Dokploy does not support `SERVICE_FQDN_*` variables. You **must** hardcode `API_URL` and `DASHBOARD_URL` with your actual domain values.
### Configure API Service Domain Settings
In Dokploy, configure the API service domain:
1. Go to the `op-api` service configuration
2. Set up the domain configuration:
```toml
[[config.domains]]
serviceName = "op-api"
port = 3000
host = "${api_domain}"
```
3. **Important**: Check the **"Strip external path"** checkbox for the API service
The "Strip external path" option is crucial! Without it, the API will receive incorrect paths when requests are forwarded from `/api`.
### Deploy
1. Review all configuration
2. Click **"Deploy"** or **"Save"**
3. Wait for all services to start and become healthy
Monitor the deployment logs to ensure all services start correctly.
### Verify Installation
Once deployment is complete:
1. Check that all services are running:
* `op-api` - API server
* `op-dashboard` - Dashboard (frontend)
* `op-worker` - Background worker
* `op-db` - PostgreSQL database
* `op-kv` - Redis cache
* `op-ch` - ClickHouse database
2. Access your dashboard at your configured domain
3. Try creating an account to verify the API is working correctly
If you're using Cloudflare in front of Dokploy, remember to purge the Cloudflare cache after making changes to ensure updated resources are served.
## Configuration Details
### Required Environment Variables
For Dokploy, you **must** hardcode these variables (unlike Coolify, Dokploy doesn't support `SERVICE_FQDN_*` variables):
* `API_URL` - Full API URL (e.g., `https://analytics.example.com/api`)
* `DASHBOARD_URL` - Full Dashboard URL (e.g., `https://analytics.example.com`)
Dokploy automatically sets:
* `OPENPANEL_POSTGRES_DB` - PostgreSQL database name
* `SERVICE_USER_POSTGRES` - PostgreSQL username
* `SERVICE_PASSWORD_POSTGRES` - PostgreSQL password (auto-generated)
* `SERVICE_PASSWORD_REDIS` - Redis password (auto-generated)
For a complete reference of all available environment variables, see the [Environment Variables documentation](/docs/self-hosting/environment-variables).
### Domain Configuration
The API and dashboard services share the same domain:
* **Dashboard**: Serves the frontend at the root path (`/`)
* **API**: Serves the API at `/api` path
**Important settings for the API service:**
* Domain: Same as dashboard domain
* Path: `/api`
* **Strip external path**: β **Must be checked**
This ensures that when requests come to `/api/*`, the path is correctly forwarded to the API service without the `/api` prefix.
## Troubleshooting
### API Requests Not Working
If API requests fail after deployment:
1. **Verify environment variables**:
```bash
# Check that API_URL is set correctly
docker exec env | grep API_URL
docker exec env | grep API_URL
```
2. **Check "Strip external path" setting**:
* Go to API service configuration in Dokploy
* Ensure "Strip external path" is **checked**
3. **Verify domain configuration**:
* API service should have path `/api`
* Dashboard service should be at root `/`
### Account Creation Not Working
If account creation fails:
1. Check API logs:
```bash
# In Dokploy, view logs for op-api service
```
2. Verify `API_URL` matches your domain:
* Should be `https://yourdomain.com/api`
* Not `http://localhost:3000` or similar
3. Check that the API service is accessible:
```bash
curl https://yourdomain.com/api/healthcheck
```
### Cloudflare Cache Issues
If you're using Cloudflare in front of Dokploy:
1. After deploying or updating, purge Cloudflare cache
2. This ensures updated resources are served immediately
3. You can do this from Cloudflare dashboard or API
### Database Connection Issues
If services can't connect to databases:
1. Verify database services are running:
* `op-db` (PostgreSQL)
* `op-kv` (Redis)
* `op-ch` (ClickHouse)
2. Check environment variables are set:
```bash
docker exec env | grep DATABASE_URL
```
3. Verify service names match in docker-compose:
* Database service names: `op-db`, `op-kv`, `op-ch`
* These should match the hostnames in connection strings
## Docker Compose Structure
The OpenPanel template includes these services:
* **op-api**: OpenPanel API server
* **op-dashboard**: OpenPanel dashboard (frontend)
* **op-worker**: Background worker for processing events
* **op-db**: PostgreSQL database
* **op-kv**: Redis cache
* **op-ch**: ClickHouse analytics database
## Differences from Coolify
The Dokploy template differs from Coolify in these ways:
1. **Environment Variables**:
* Dokploy does not support `SERVICE_FQDN_*` variables
* Must hardcode `API_URL` and `DASHBOARD_URL`
2. **Domain Configuration**:
* Must manually configure domain paths
* Must enable "Strip external path" for API service
3. **Service Discovery**:
* Uses standard Docker Compose service names
* No automatic FQDN resolution
## Updating OpenPanel
To update OpenPanel in Dokploy:
1. Pull the latest images:
```bash
docker compose pull
```
2. Restart services:
```bash
docker compose up -d
```
Or use Dokploy's UI to restart services.
## Next Steps
* [Configure email settings](/docs/self-hosting/self-hosting#e-mail) for password resets and invitations
* [Set up AI integration](/docs/self-hosting/self-hosting#ai-integration) for the analytics assistant
* [Configure SDK](/docs/self-hosting/self-hosting#always-use-correct-api-url) to track events from your applications
## Additional Resources
* [Dokploy Documentation](https://docs.dokploy.com/docs/core)
* [OpenPanel GitHub Issue #292](https://github.com/Dokploy/templates/issues/292) - Discussion about Dokploy deployment
---
## Deploy on Kubernetes
URL: https://openpanel.dev/docs/self-hosting/deploy-kubernetes
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';
OpenPanel can be deployed on Kubernetes using the community-maintained Helm chart. This allows you to run OpenPanel in a scalable, production-ready Kubernetes environment.
The Helm chart is maintained by the community and available on [Artifact Hub](https://artifacthub.io/packages/helm/openpanel/openpanel).
## Prerequisites
* Kubernetes 1.19+
* Helm 3.0+
* `kubectl` configured to access your cluster
* At least 2GB RAM per node (4GB+ recommended)
* Persistent volume support (if using self-hosted databases)
## Quick Start
### Add the Helm Repository
Add the OpenPanel Helm repository:
```bash
helm repo add openpanel https://yashGoyal40.github.io/openpanel
helm repo update
```
### Download Default Values
Download the default values file to customize your configuration:
```bash
helm show values openpanel/openpanel > my-values.yaml
```
β οΈ **IMPORTANT**: Before installing, you **MUST** configure the required values in `values.yaml`. The chart includes placeholder values (marked with `<>`) that will cause the installation to fail if not properly configured.
### Configure Required Values
Edit `my-values.yaml` and configure the following **required** values:
1. **Ingress Configuration**:
```yaml
ingress:
enabled: true
type: standard # or "httpproxy" for Contour
fqdn: your-domain.com # Replace with your actual domain
standard:
tlsSecretName: openpanel-tls
```
2. **Application URLs**:
```yaml
config:
apiUrl: "https://your-domain.com/api"
dashboardUrl: "https://your-domain.com"
googleRedirectUri: "https://your-domain.com/api/oauth/google/callback"
```
3. **Cookie Secret** (generate with `openssl rand -base64 32`):
```yaml
secrets:
cookieSecret: "YOUR_GENERATED_SECRET_HERE"
```
4. **PostgreSQL Configuration** (choose one):
* **Option A**: External PostgreSQL (recommended for production)
```yaml
postgresql:
enabled: false
externalPostgresql:
host: "postgres.example.com"
port: 5432
user: "openpanel"
password: "your-secure-password"
database: "openpanel"
schema: public
```
* **Option B**: Self-hosted PostgreSQL
```yaml
postgresql:
enabled: true
user: postgres
password: "your-secure-password"
database: postgres
persistence:
size: 20Gi
```
### Install OpenPanel
Install OpenPanel with your configured values:
```bash
helm install my-openpanel openpanel/openpanel \
--version 0.1.0 \
--namespace openpanel \
--create-namespace \
-f my-values.yaml
```
Or override specific values directly:
```bash
helm install my-openpanel openpanel/openpanel \
--version 0.1.0 \
--namespace openpanel \
--create-namespace \
--set ingress.fqdn=your-domain.com \
--set config.apiUrl=https://your-domain.com/api \
--set secrets.cookieSecret=$(openssl rand -base64 32)
```
### Verify Installation
Check that all pods are running:
```bash
kubectl get pods -n openpanel
```
You should see pods for:
* API server (`op-api`)
* Dashboard (`op-dashboard`)
* Worker (`op-worker`)
* PostgreSQL (if using self-hosted)
* Redis (if using self-hosted)
* ClickHouse (if using self-hosted)
Check the status:
```bash
kubectl get all -n openpanel
```
### Access Your Dashboard
Once all pods are running, access OpenPanel at your configured domain. The ingress will route traffic to the dashboard service.
If you need to test locally, you can port-forward:
```bash
kubectl port-forward svc/op-dashboard 3000:80 -n openpanel
```
Then access OpenPanel at `http://localhost:3000`.
## Configuration
### Required Configuration
The following values **MUST** be configured before installation:
| Configuration | Required | Placeholder | Description |
| -------------------------- | -------------- | ------------------- | ----------------------------- |
| `ingress.fqdn` | β Yes | `` | Your domain name |
| `ingress.*.tlsSecretName` | β Yes | `` | TLS certificate secret name |
| `config.apiUrl` | β Yes | `` | Full API URL |
| `config.dashboardUrl` | β Yes | `` | Full Dashboard URL |
| `config.googleRedirectUri` | β Yes | `` | OAuth callback URL |
| `secrets.cookieSecret` | β Yes | `CHANGE_ME_...` | Session encryption key |
| `externalPostgresql.*` | β οΈ If external | `` | PostgreSQL connection details |
### Complete Example Configuration
Here's a minimal example configuration file (`my-values.yaml`) with all required values:
```yaml title="my-values.yaml"
# Ingress Configuration
ingress:
enabled: true
type: standard # or "httpproxy" for Contour
fqdn: analytics.example.com
standard:
tlsSecretName: openpanel-tls
# Application URLs
config:
apiUrl: "https://analytics.example.com/api"
dashboardUrl: "https://analytics.example.com"
googleRedirectUri: "https://analytics.example.com/api/oauth/google/callback"
# Cookie Secret (generate with: openssl rand -base64 32)
secrets:
cookieSecret: "YOUR_GENERATED_SECRET_HERE"
# PostgreSQL - Using External Database
postgresql:
enabled: false
externalPostgresql:
host: "postgres.example.com"
port: 5432
user: "openpanel"
password: "your-secure-password"
database: "openpanel"
schema: public
```
### Optional Configuration
The Helm chart maps environment variables to Helm values. For a complete reference of all available environment variables and their descriptions, see the [Environment Variables documentation](/docs/self-hosting/environment-variables).
#### Email Configuration
Enable email functionality (password resets, invitations, etc.):
```yaml
secrets:
resendApiKey: "re_xxxxxxxxxxxxx" # Your Resend API key
emailSender: "noreply@your-domain.com" # Verified sender email
```
Get your Resend API key from [resend.com](https://resend.com). Make sure to verify your sender email domain.
#### AI Features
Enable AI-powered features:
```yaml
config:
aiModel: "gpt-4o-mini" # Options: gpt-4o, gpt-4o-mini, claude-3-5
secrets:
openaiApiKey: "sk-xxxxxxxxxxxxx" # For OpenAI models
anthropicApiKey: "" # For Claude models (leave empty if not using)
geminiApiKey: "" # For Gemini models (leave empty if not using)
```
You only need to configure the API key for the model you choose. Leave other API keys as empty strings (`""`) if not using them.
#### Google OAuth
Enable Google OAuth login:
```yaml
secrets:
googleClientId: "xxxxx.apps.googleusercontent.com"
googleClientSecret: "GOCSPX-xxxxxxxxxxxxx"
```
Set up Google OAuth in [Google Cloud Console](https://console.cloud.google.com). Add authorized redirect URI: `https://your-domain.com/api/oauth/google/callback`
#### Redis Configuration
Redis is enabled by default and deployed within Kubernetes. To use an external Redis instance:
```yaml
redis:
enabled: false
externalRedis:
host: "redis.example.com"
port: 6379
```
#### ClickHouse Configuration
ClickHouse is enabled by default and deployed within Kubernetes. To use an external ClickHouse instance:
```yaml
clickhouse:
enabled: false
externalClickhouse:
host: "clickhouse.example.com"
port: 8123
database: openpanel
```
#### Application Components
Enable/disable individual components:
```yaml
api:
enabled: true
replicas: 1
dashboard:
enabled: true
replicas: 1
worker:
enabled: true
replicas: 1
```
#### Resource Limits
Adjust resource requests and limits:
```yaml
api:
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "2Gi"
cpu: "2000m"
```
## Updating OpenPanel
To upgrade to a newer version:
```bash
helm repo update
helm upgrade my-openpanel openpanel/openpanel \
--version \
--namespace openpanel \
-f my-values.yaml
```
Replace `` with the desired version number (e.g., `0.1.1`).
## Managing Your Deployment
### View Logs
View logs from specific deployments:
```bash
# API logs
kubectl logs -f deployment/op-api -n openpanel
# Dashboard logs
kubectl logs -f deployment/op-dashboard -n openpanel
# Worker logs
kubectl logs -f deployment/op-worker -n openpanel
```
### Restart Services
Restart a specific deployment:
```bash
kubectl rollout restart deployment/op-api -n openpanel
kubectl rollout restart deployment/op-dashboard -n openpanel
kubectl rollout restart deployment/op-worker -n openpanel
```
### Scale Services
Scale services on the fly:
```bash
kubectl scale deployment/op-worker --replicas=3 -n openpanel
```
Or update your values file and upgrade:
```yaml
worker:
replicas: 3
```
```bash
helm upgrade my-openpanel openpanel/openpanel -f my-values.yaml -n openpanel
```
### Check Services
View all services:
```bash
kubectl get svc -n openpanel
```
### Check ConfigMap and Secrets
Verify configuration:
```bash
kubectl get configmap openpanel-config -n openpanel -o yaml
kubectl get secret openpanel-secrets -n openpanel -o yaml
```
## Ingress Configuration
### Standard Ingress (NGINX/Traefik)
```yaml
ingress:
enabled: true
type: standard
fqdn: openpanel.your-domain.com
standard:
tlsSecretName: openpanel-tls
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
```
### HTTPProxy (Contour)
```yaml
ingress:
enabled: true
type: httpproxy
fqdn: openpanel.your-domain.com
httpproxy:
tlsSecretName: openpanel-tls
```
## Troubleshooting
### Pods Not Starting
1. Check pod status:
```bash
kubectl describe pod -n openpanel
```
2. Check events:
```bash
kubectl get events --sort-by='.lastTimestamp' -n openpanel
```
3. Check logs:
```bash
kubectl logs -n openpanel
```
### Database Connection Issues
1. Verify database pods are running (if using self-hosted):
```bash
kubectl get pods -n openpanel | grep postgres
```
2. Check database service:
```bash
kubectl get svc -n openpanel | grep postgres
```
3. Test database connection:
```bash
kubectl exec -it deployment/op-api -n openpanel -- env | grep DATABASE_URL
```
### Configuration Issues
If pods are failing due to configuration:
1. Verify all required values are set:
```bash
helm get values my-openpanel -n openpanel
```
2. Check for placeholder values:
```bash
helm get values my-openpanel -n openpanel | grep "<"
```
3. Ensure secrets are properly set:
```bash
kubectl get secret openpanel-secrets -n openpanel -o yaml
```
### Ingress Not Working
1. Check ingress status:
```bash
kubectl get ingress -n openpanel
kubectl describe ingress -n openpanel
```
2. Verify ingress controller is running:
```bash
kubectl get pods -n ingress-nginx # For NGINX
# or
kubectl get pods -n projectcontour # For Contour
```
3. Check DNS configuration
## Backup and Restore
### Backup PostgreSQL
If using self-hosted PostgreSQL:
```bash
kubectl exec -it -n openpanel -- \
pg_dump -U postgres openpanel > backup.sql
```
Or use a Kubernetes CronJob for automated backups.
### Restore PostgreSQL
Restore from backup:
```bash
kubectl exec -i -n openpanel -- \
psql -U postgres openpanel < backup.sql
```
## Uninstalling
To uninstall OpenPanel:
```bash
helm uninstall my-openpanel --namespace openpanel
```
β οΈ **Warning**: This will delete all resources including persistent volumes. Make sure to backup your data before uninstalling!
To keep persistent volumes:
```bash
# Delete the release but keep PVCs
helm uninstall my-openpanel --namespace openpanel
# Manually delete PVCs if needed
kubectl delete pvc -l app.kubernetes.io/name=openpanel -n openpanel
```
## Next Steps
* [Configure email settings](/docs/self-hosting/self-hosting#e-mail) for password resets and invitations
* [Set up AI integration](/docs/self-hosting/self-hosting#ai-integration) for the analytics assistant
* [Configure SDK](/docs/self-hosting/self-hosting#always-use-correct-api-url) to track events from your applications
## Additional Resources
* [Helm Chart on Artifact Hub](https://artifacthub.io/packages/helm/openpanel/openpanel)
* [Kubernetes Documentation](https://kubernetes.io/docs/)
* [Helm Documentation](https://helm.sh/docs/)
---
## Environment Variables
URL: https://openpanel.dev/docs/self-hosting/environment-variables
import { Callout } from 'fumadocs-ui/components/callout';
This page documents all environment variables used by OpenPanel. Variables are organized by category. Most variables are optional and have sensible defaults.
For deployment-specific configuration, see the [deployment guides](/docs/self-hosting/deploy-docker-compose).
## Database & Storage
### DATABASE\_URL
**Type**: `string`\
**Required**: Yes\
**Default**: None
PostgreSQL connection string for the main database.
**Example**:
```bash
DATABASE_URL=postgres://user:password@localhost:5432/openpanel?schema=public
```
### DATABASE\_URL\_DIRECT
**Type**: `string`\
**Required**: No\
**Default**: Same as `DATABASE_URL`
Direct PostgreSQL connection string (bypasses connection pooling). Used for migrations and administrative operations.
**Example**:
```bash
DATABASE_URL_DIRECT=postgres://user:password@localhost:5432/openpanel?schema=public
```
### DATABASE\_URL\_REPLICA
**Type**: `string`\
**Required**: No\
**Default**: Same as `DATABASE_URL`
Read replica connection string for read-heavy operations. If not set, uses the main database.
**Example**:
```bash
DATABASE_URL_REPLICA=postgres://user:password@replica-host:5432/openpanel?schema=public
```
### REDIS\_URL
**Type**: `string`\
**Required**: Yes\
**Default**: `redis://localhost:6379`
Redis connection string for caching and queue management.
**Example**:
```bash
REDIS_URL=redis://localhost:6379
# With password
REDIS_URL=redis://:password@localhost:6379
```
### CLICKHOUSE\_URL
**Type**: `string`\
**Required**: Yes\
**Default**: `http://localhost:8123/openpanel`
ClickHouse HTTP connection URL for analytics data storage.
**Example**:
```bash
CLICKHOUSE_URL=http://localhost:8123/openpanel
```
### CLICKHOUSE\_CLUSTER
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Enable ClickHouse cluster mode. Set to `true` or `1` if using a ClickHouse cluster.
**Example**:
```bash
CLICKHOUSE_CLUSTER=true
```
### CLICKHOUSE\_SETTINGS
**Type**: `string` (JSON)\
**Required**: No\
**Default**: `{}`
Additional ClickHouse settings as a JSON object.
**Example**:
```bash
CLICKHOUSE_SETTINGS='{"max_execution_time": 300}'
```
### CLICKHOUSE\_SETTINGS\_REMOVE\_CONVERT\_ANY\_JOIN
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Remove `convert_any_join` from ClickHouse settings. Used for compatibility with certain ClickHouse versions. This needs to be set if you use any clickhouse version below 25!
## Application URLs
### API\_URL
**Type**: `string`\
**Required**: Yes\
**Default**: None
Public API URL exposed to the browser. Used by the dashboard frontend and API service.
**Example**:
```bash
API_URL=https://analytics.example.com/api
```
### DASHBOARD\_URL
**Type**: `string`\
**Required**: Yes\
**Default**: None
Public dashboard URL exposed to the browser. Used by the dashboard frontend and API service.
**Example**:
```bash
DASHBOARD_URL=https://analytics.example.com
```
### API\_CORS\_ORIGINS
**Type**: `string` (comma-separated)\
**Required**: No\
**Default**: None
Additional CORS origins allowed for API requests. Comma-separated list of origins.
**Example**:
```bash
API_CORS_ORIGINS=https://app.example.com,https://another-app.com
```
## Authentication & Security
### COOKIE\_SECRET
**Type**: `string`\
**Required**: Yes\
**Default**: None
Secret key for encrypting session cookies. Generate a secure random string (32+ characters).
**Example**:
```bash
# Generate with: openssl rand -base64 32
COOKIE_SECRET=your-random-secret-here
```
Never use the default value in production! Always generate a unique secret.
### COOKIE\_TLDS
**Type**: `string` (comma-separated)\
**Required**: No\
**Default**: None
Custom multi-part TLDs for cookie domain handling. Use this when deploying on domains with public suffixes that aren't recognized by default (e.g., `.my.id`, `.web.id`, `.co.id`).
**Example**:
```bash
# For domains like abc.my.id
COOKIE_TLDS=my.id
# Multiple TLDs
COOKIE_TLDS=my.id,web.id,co.id
```
This is required when using domain suffixes that are public suffixes (like `.co.uk`). Without this, the browser will reject authentication cookies. Common examples include Indonesian domains (`.my.id`, `.web.id`, `.co.id`).
### CUSTOM\_COOKIE\_DOMAIN
**Type**: `string`\
**Required**: No\
**Default**: None
Override the automatic cookie domain detection and set a specific domain for authentication cookies. Useful when proxying the API through your main domain or when you need precise control over cookie scope.
**Example**:
```bash
# Set cookies only on the main domain
CUSTOM_COOKIE_DOMAIN=.example.com
# Set cookies on a specific subdomain
CUSTOM_COOKIE_DOMAIN=.app.example.com
```
When set, this completely bypasses the automatic domain parsing logic. The cookie will always be set as secure. Include a leading dot (`.`) to allow the cookie to be shared across subdomains.
### DEMO\_USER\_ID
**Type**: `string`\
**Required**: No\
**Default**: None
User ID for demo mode. When set, creates a demo session for testing.
**Example**:
```bash
DEMO_USER_ID=user_1234567890
```
### ALLOW\_REGISTRATION
**Type**: `boolean`\
**Required**: No\
**Default**: `false` (after first user is created)
Allow new user registrations. Set to `true` to enable public registration.
**Example**:
```bash
ALLOW_REGISTRATION=true
```
Registration is automatically disabled after the first user is created. Set this to `true` to re-enable it.
### ALLOW\_INVITATION
**Type**: `boolean`\
**Required**: No\
**Default**: `true`
Allow user invitations. Set to `false` to disable invitation functionality.
**Example**:
```bash
ALLOW_INVITATION=false
```
## AI Features
### AI\_MODEL
**Type**: `string`\
**Required**: No\
**Default**: `gpt-4.1-mini`
AI model to use for the analytics assistant. Options: `gpt-4o`, `gpt-4o-mini`, `claude-3-5`.
**Example**:
```bash
AI_MODEL=gpt-4o-mini
```
### OPENAI\_API\_KEY
**Type**: `string`\
**Required**: No (required if using OpenAI models)\
**Default**: None
OpenAI API key for AI features. Required if `AI_MODEL` is set to `gpt-4o` or `gpt-4o-mini`.
**Example**:
```bash
OPENAI_API_KEY=sk-your-openai-api-key-here
```
### ANTHROPIC\_API\_KEY
**Type**: `string`\
**Required**: No (required if using Claude models)\
**Default**: None
Anthropic API key for Claude AI models. Required if `AI_MODEL` is set to `claude-3-5`.
**Example**:
```bash
ANTHROPIC_API_KEY=your-anthropic-api-key-here
```
### GEMINI\_API\_KEY
**Type**: `string`\
**Required**: No\
**Default**: None
Google Gemini API key for Gemini AI models. Currently not used but reserved for future support.
**Example**:
```bash
GEMINI_API_KEY=your-gemini-api-key-here
```
The AI assistant is optional. Without an API key configured, the AI chat feature will not be available, but all other OpenPanel features will continue to work normally.
## Email
### RESEND\_API\_KEY
**Type**: `string`\
**Required**: No\
**Default**: None
Resend API key for sending transactional emails (password resets, invitations, etc.).
**Example**:
```bash
RESEND_API_KEY=re_xxxxxxxxxxxxx
```
Get your API key from [resend.com](https://resend.com). Make sure to verify your sender email domain.
### EMAIL\_SENDER
**Type**: `string`\
**Required**: No\
**Default**: `hello@openpanel.dev`
Email address used as the sender for transactional emails.
**Example**:
```bash
EMAIL_SENDER=noreply@yourdomain.com
```
The sender email must be verified in your Resend account.
## OAuth & Integrations
### SLACK\_CLIENT\_ID
**Type**: `string`\
**Required**: No\
**Default**: None
Slack OAuth client ID for Slack integration.
**Example**:
```bash
SLACK_CLIENT_ID=1234567890.1234567890
```
### SLACK\_CLIENT\_SECRET
**Type**: `string`\
**Required**: No\
**Default**: None
Slack OAuth client secret for Slack integration.
**Example**:
```bash
SLACK_CLIENT_SECRET=your-slack-client-secret
```
### SLACK\_OAUTH\_REDIRECT\_URL
**Type**: `string`\
**Required**: No\
**Default**: None
Slack OAuth redirect URL. Must match the redirect URI configured in your Slack app.
**Example**:
```bash
SLACK_OAUTH_REDIRECT_URL=https://analytics.example.com/api/integrations/slack/callback
```
### SLACK\_STATE\_SECRET
**Type**: `string`\
**Required**: No\
**Default**: None
Secret for signing Slack OAuth state parameter.
**Example**:
```bash
SLACK_STATE_SECRET=your-state-secret
```
## Self-hosting
### SELF\_HOSTED
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Enable self-hosted mode. Set to `true` or `1` to enable self-hosting features. Used by both the dashboard frontend and API service.
**Example**:
```bash
SELF_HOSTED=true
```
## Worker & Queue
### WORKER\_PORT
**Type**: `number`\
**Required**: No\
**Default**: `3000`
Port for the worker service to listen on.
**Example**:
```bash
WORKER_PORT=3000
```
### DISABLE\_BULLBOARD
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Disable BullMQ board UI. Set to `true` or `1` to disable the queue monitoring dashboard.
**Example**:
```bash
DISABLE_BULLBOARD=true
```
### DISABLE\_WORKERS
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Disable all worker processes. Set to `true` or `1` to disable background job processing.
**Example**:
```bash
DISABLE_WORKERS=true
```
### ENABLED\_QUEUES
**Type**: `string` (comma-separated)\
**Required**: No\
**Default**: All queues enabled
Comma-separated list of queue names to enable. Available queues: `events`, `sessions`, `cron`, `notification`, `misc`, `import`.
**Example**:
```bash
ENABLED_QUEUES=events,sessions,cron
```
### EVENT\_JOB\_CONCURRENCY
**Type**: `number`\
**Required**: No\
**Default**: `10`
Number of concurrent event processing jobs per worker.
**Example**:
```bash
EVENT_JOB_CONCURRENCY=20
```
### EVENT\_BLOCKING\_TIMEOUT\_SEC
**Type**: `number`\
**Required**: No\
**Default**: `1`
Blocking timeout in seconds for event queue workers.
**Example**:
```bash
EVENT_BLOCKING_TIMEOUT_SEC=2
```
### EVENTS\_GROUP\_QUEUES\_SHARDS
**Type**: `number`\
**Required**: No\
**Default**: `1`
Number of shards for the events group queue. Increase for better performance with high event volume.
**Example**:
```bash
EVENTS_GROUP_QUEUES_SHARDS=4
```
### QUEUE\_CLUSTER
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Enable Redis cluster mode for queues. When enabled, queue names are wrapped with `{}` for Redis cluster sharding.
**Example**:
```bash
QUEUE_CLUSTER=true
```
### ORDERING\_DELAY\_MS
**Type**: `number`\
**Required**: No\
**Default**: `100`
Delay in milliseconds to hold events for correct ordering when events arrive out of order.
**Example**:
```bash
ORDERING_DELAY_MS=200
```
Should not exceed 500ms. Higher values may cause delays in event processing.
### AUTO\_BATCH\_MAX\_WAIT\_MS
**Type**: `number`\
**Required**: No\
**Default**: `0` (disabled)
Maximum wait time in milliseconds for auto-batching events. Experimental feature.
**Example**:
```bash
AUTO_BATCH_MAX_WAIT_MS=100
```
β οΈ **Experimental**: This feature is experimental and not used in production. Do not use unless you have a good understanding of the implications and specific performance requirements.
### AUTO\_BATCH\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: `0` (disabled)
Batch size for auto-batching events. Experimental feature.
**Example**:
```bash
AUTO_BATCH_SIZE=100
```
β οΈ **Experimental**: This feature is experimental and not used in production. Do not use unless you have a good understanding of the implications and specific performance requirements.
## Buffers
### SESSION\_BUFFER\_BATCH\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: Buffer-specific default
Batch size for session buffer operations.
**Example**:
```bash
SESSION_BUFFER_BATCH_SIZE=5000
```
### SESSION\_BUFFER\_CHUNK\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: Buffer-specific default
Chunk size for session buffer operations.
**Example**:
```bash
SESSION_BUFFER_CHUNK_SIZE=1000
```
### EVENT\_BUFFER\_BATCH\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: `4000`
Batch size for event buffer operations.
**Example**:
```bash
EVENT_BUFFER_BATCH_SIZE=5000
```
### EVENT\_BUFFER\_CHUNK\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: `1000`
Chunk size for event buffer operations.
**Example**:
```bash
EVENT_BUFFER_CHUNK_SIZE=2000
```
### PROFILE\_BUFFER\_BATCH\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: Buffer-specific default
Batch size for profile buffer operations.
**Example**:
```bash
PROFILE_BUFFER_BATCH_SIZE=5000
```
### PROFILE\_BUFFER\_CHUNK\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: Buffer-specific default
Chunk size for profile buffer operations.
**Example**:
```bash
PROFILE_BUFFER_CHUNK_SIZE=1000
```
### PROFILE\_BUFFER\_TTL\_IN\_SECONDS
**Type**: `number`\
**Required**: No\
**Default**: Buffer-specific default
Time-to-live in seconds for profile buffer entries.
**Example**:
```bash
PROFILE_BUFFER_TTL_IN_SECONDS=3600
```
### BOT\_BUFFER\_BATCH\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: Buffer-specific default
Batch size for bot detection buffer operations.
**Example**:
```bash
BOT_BUFFER_BATCH_SIZE=1000
```
## Performance & Tuning
### IMPORT\_BATCH\_SIZE
**Type**: `number`\
**Required**: No\
**Default**: `5000`
Batch size for data import operations.
**Example**:
```bash
IMPORT_BATCH_SIZE=10000
```
### IP\_HEADER\_ORDER
**Type**: `string` (comma-separated)\
**Required**: No\
**Default**: See [default order](https://github.com/Openpanel-dev/openpanel/blob/main/packages/common/server/get-client-ip.ts)
Custom order of HTTP headers to check for client IP address. Useful when behind specific proxies or CDNs.
**Example**:
```bash
IP_HEADER_ORDER=cf-connecting-ip,x-real-ip,x-forwarded-for
```
The default order includes: `openpanel-client-ip`, `cf-connecting-ip`, `true-client-ip`, `x-client-ip`, `x-forwarded-for`, `x-real-ip`, and others. See the [source code](https://github.com/Openpanel-dev/openpanel/blob/main/packages/common/server/get-client-ip.ts) for the complete default list.
### SHUTDOWN\_GRACE\_PERIOD\_MS
**Type**: `number`\
**Required**: No\
**Default**: `5000`
Grace period in milliseconds for graceful shutdown of services.
**Example**:
```bash
SHUTDOWN_GRACE_PERIOD_MS=10000
```
### API\_PORT
**Type**: `number`\
**Required**: No\
**Default**: `3000`
Port for the API service to listen on.
**Example**:
```bash
API_PORT=3000
```
### API\_HOST
**Type**: `string`\
**Required**: No\
**Default**: `0.0.0.0` (production) / `localhost` (development)
Host address for the API service to bind to. Set to `::` to enable IPv6 support (useful for platforms like Railway that use IPv6 for internal networking).
**Example**:
```bash
# Default IPv4 only
API_HOST=0.0.0.0
# IPv6 (dual-stack, accepts both IPv4 and IPv6)
API_HOST=::
```
Use `API_HOST=::` when deploying on platforms like Railway where private networking requires IPv6. The `::` address enables dual-stack mode, accepting both IPv4 and IPv6 connections on most systems.
## Logging
### LOG\_LEVEL
**Type**: `string`\
**Required**: No\
**Default**: `info`
Logging level. Options: `error`, `warn`, `info`, `debug`.
**Example**:
```bash
LOG_LEVEL=debug
```
### LOG\_SILENT
**Type**: `boolean`\
**Required**: No\
**Default**: `false`
Disable all logging output. Set to `true` to silence logs.
**Example**:
```bash
LOG_SILENT=true
```
### LOG\_PREFIX
**Type**: `string`\
**Required**: No\
**Default**: None
Prefix for log messages. Useful for identifying logs from different services.
**Example**:
```bash
LOG_PREFIX=api
```
### HYPERDX\_API\_KEY
**Type**: `string`\
**Required**: No\
**Default**: None
HyperDX API key for sending logs to HyperDX for monitoring and analysis.
**Example**:
```bash
HYPERDX_API_KEY=your-hyperdx-api-key
```
## Geo
### MAXMIND\_LICENSE\_KEY
**Type**: `string`\
**Required**: No\
**Default**: None
MaxMind GeoLite2 license key for downloading GeoIP databases.
**Example**:
```bash
MAXMIND_LICENSE_KEY=your-maxmind-license-key
```
Get your license key from [MaxMind](https://www.maxmind.com/en/accounts/current/license-key). Required for downloading GeoIP databases.
## Quick Reference
### Required Variables
For a basic self-hosted installation, these variables are required:
* `DATABASE_URL` - PostgreSQL connection
* `REDIS_URL` - Redis connection
* `CLICKHOUSE_URL` - ClickHouse connection
* `API_URL` - API endpoint URL
* `DASHBOARD_URL` - Dashboard URL
* `COOKIE_SECRET` - Session encryption secret
### Optional but Recommended
* `RESEND_API_KEY` - For email features
* `EMAIL_SENDER` - Email sender address
* `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` - For AI features
* `AI_MODEL` - AI model selection
### See Also
* [Deploy with Docker Compose](/docs/self-hosting/deploy-docker-compose)
* [Deploy with Coolify](/docs/self-hosting/deploy-coolify)
* [Deploy with Dokploy](/docs/self-hosting/deploy-dokploy)
* [Deploy on Kubernetes](/docs/self-hosting/deploy-kubernetes)
---
## Migrating from Clerk
URL: https://openpanel.dev/docs/self-hosting/migrating-from-clerk
import { Step, Steps } from 'fumadocs-ui/components/steps';
As of version 0.0.5, we have removed Clerk.com from OpenPanel. This means that if you are upgrading from a previous version, you will need to export your users from Clerk and import them into OpenPanel. Here is how you can do it.
Before we start lets get the users from Clerk. Go to **Clerk > Configure > Settings > Export all users** and download the CSV file. This file will be used to import the users into OpenPanel.
Copy the csv file we downloaded from Clerk to your server:
```bash
scp ./path/to/your/clerk-users.csv user@your-ip:users-dump.csv
```
SSH into your server:
```bash
ssh user@your-ip
```
Pull the latest images, and restart the containers:
```bash
docker compose pull
docker compose down
docker compose up -d
```
SSH into your server:
```bash
ssh user@your-ip
```
Run the following command to copy the file to the OpenPanel container:
```bash
docker compose cp ./users-dump.csv op-api:/app/packages/db/code-migrations/users-dump.csv
```
Run the migration:
```bash
docker compose exec -it op-api bash -c "cd /app/packages/db && pnpm migrate:deploy:code 2-accounts.ts"
```
---
## Get started with self-hosting
URL: https://openpanel.dev/docs/self-hosting/self-hosting
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Instructions
### Prerequisites
* VPS of any kind (only tested on Ubuntu 24.04)
* We recommend using [Hetzner (affiliate link)](https://hetzner.cloud/?ref=7Hq0H5mQh7tM). Use the link if you want to support us. π«Ά
* πββοΈ This should work on any system if you have pre-installed docker, node and pnpm
### Quickstart
```bash
git clone -b self-hosting https://github.com/Openpanel-dev/openpanel && cd openpanel/self-hosting && ./setup
# After setup is complete run `./start` to start OpenPanel
```
### Clone
Clone the repository to your VPS and checkout the self-hosting tag
```bash
git clone -b self-hosting https://github.com/Openpanel-dev/openpanel.git
```
### Run the setup script
The setup script will do 3 things
1. Install node (if you accept)
2. Install docker (if you accept)
3. Execute a node script that will ask some questions about your setup
> Setup takes 30s to 2 minutes depending on your VPS
```bash
cd openpanel/self-hosting
./setup
```
β οΈ If the `./setup` script fails to run, you can do it manually.
1. Install docker
2. Install node
3. Install npm
4. Run the `npm run quiz` script inside the self-hosting folder
### Start π
Run the `./start` script located inside the self-hosting folder
```bash
./start
```
## Good to know
### Always use correct api url
When self-hosting you'll need to provide your api url when initializing the SDK.
The path should be `/api` and the domain should be your domain.
```html title="index.html"
```
```js title="op.ts"
import { OpenPanel } from '@openpanel/sdk';
const op = new OpenPanel({
apiUrl: 'https://your-domain.com/api', // [!code highlight]
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
});
```
### E-mail
Some of OpenPanel's features require e-mail. We use Resend as our transactional e-mail provider. To enable email features, create an account on Resend and set the `RESEND_API_KEY` environment variable.
This is nothing that is required for the basic setup, but it is required for some features.
Features that require e-mail:
* Password reset
* Invitations
* more will be added over time
For email configuration details, see the [Environment Variables documentation](/docs/self-hosting/environment-variables#email).
### AI integration
OpenPanel includes an AI-powered analytics assistant that can help you analyze data, create reports, and answer questions about your analytics. To enable this feature, you need to configure an API key for either OpenAI or Anthropic.
#### Supported Models
* **OpenAI** (default)
* `gpt-4o` - More powerful but higher cost
* `gpt-4o-mini` - Default model, good balance of performance and cost
* **Anthropic**
* `claude-3-5-haiku-latest` - Fast and cost-effective
#### Configuration
Add the following environment variables to your `.env` file in the `self-hosting` directory:
**For OpenAI (default):**
```bash title=".env"
OPENAI_API_KEY=sk-your-openai-api-key-here
AI_MODEL=gpt-4o-mini # Optional: defaults to gpt-4o-mini
```
**For Anthropic:**
```bash title=".env"
ANTHROPIC_API_KEY=your-anthropic-api-key-here
AI_MODEL=claude-3-5
```
#### Getting API Keys
* **OpenAI**: Get your API key from [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
* **Anthropic**: Get your API key from [console.anthropic.com](https://console.anthropic.com)
The AI assistant is optional. Without an API key configured, the AI chat feature will not be available, but all other OpenPanel features will continue to work normally.
AI features will incur costs based on your usage and the model you choose. Monitor your API usage through your provider's dashboard to avoid unexpected charges.
For complete AI configuration details, see the [Environment Variables documentation](/docs/self-hosting/environment-variables#ai-features).
### Managed Redis
If you use a managed Redis service, you may need to set the `notify-keyspace-events` manually.
Without this setting we won't be able to listen for expired keys which we use for calculating currently active visitors.
> You will see a warning in the logs if this needs to be set manually.
### Registration / Invitations
By default registrations are disabled after the first user is created.
You can change this by setting the `ALLOW_REGISTRATION` environment variable to `true`.
```bash title=".env"
ALLOW_REGISTRATION=true
```
Invitations are enabled by default. You can also disable invitations by setting the `ALLOW_INVITATION` environment variable to `false`.
```bash title=".env"
ALLOW_INVITATION=false
```
For a complete reference of all environment variables, see the [Environment Variables documentation](/docs/self-hosting/environment-variables).
## Helpful scripts
OpenPanel comes with several utility scripts to help manage your self-hosted instance:
### Basic Operations
```bash
./start # Start all OpenPanel services
./stop # Stop all OpenPanel services
./logs # View real-time logs from all services
```
### Maintenance
```bash
./rebuild # Rebuild and restart a specific service
# Example: ./rebuild op-dashboard
```
### Troubleshooting
```bash
./danger_wipe_everything # β οΈ Removes all containers, volumes, and data
# Only use this if you want to start fresh!
```
The `danger_wipe_everything` script will delete all your OpenPanel data including databases, configurations, and cached files. Use with extreme caution!
All these scripts should be run from within the `self-hosting` directory. Make sure the scripts are executable (`chmod +x script-name` if needed).
## Updating
To grab the latest and greatest from OpenPanel you should just run the `./update` script inside the self-hosting folder.
If you don't have the `./update` script, you can run `git pull` and then `./update`
Also read any changes in the [changelog](/docs/self-hosting/changelog) and apply them to your instance.
---
## Latest Docker Images
URL: https://openpanel.dev/docs/self-hosting/supporter-access-latest-docker-images
## Thank you for your support! π
First and foremost, **thank you** for supporting OpenPanel! Your contribution means the world to us and helps keep this project alive, maintained, and constantly improving. Every dollar you contribute goes directly into development, infrastructure, and making OpenPanel better for everyone.
As a supporter, you get exclusive access to our private Docker images that are built with every commit, giving you the absolute latest features and fixes before they're publicly released.
## Why latest images matter
### Bleeding-edge features
* **Instant access**: Get new features the moment they're merged
* **Early bug fixes**: Patches and fixes deployed immediately
* **Continuous improvements**: Performance enhancements and optimizations in real-time
* **Stay ahead**: Run the most advanced version of OpenPanel available
### Built on every commit
We maintain a continuous integration pipeline that builds new Docker images with every single commit to our repository. This means:
* Zero delay between development and deployment
* Production-ready images tested and validated automatically
* Access to features weeks or months before stable releases
## How it works
Our private Docker images are hosted on GitHub's container registry and protected from public access. As a supporter, you get an API key that grants you access to our private Docker proxy at `docker.openpanel.dev`, which seamlessly pulls these images for you.
## Getting started
### Step 1: Become a supporter
[Become a supporter](/supporter) to get access to exclusive Docker images. Support starts at just **$20/month** and includes:
* Access to all private Docker images
* Priority support in our Discord community
* Direct impact on OpenPanel's development
* Our eternal gratitude β€οΈ
### Step 2: Get your API key
Once you become a supporter, you'll receive a unique API key that grants access to our Docker proxy.
### Step 3: Login to docker registry
On your server, authenticate with our Docker proxy using your API key:
```bash
echo "your_api_key" | docker login docker.openpanel.dev -u user --password-stdin
```
Replace `your_api_key` with the actual API key provided to you.
Make sure to keep your API key secure and never commit it to version control!
### Step 4: Update to latest images
We've created a convenient script to make updating your images effortless. Navigate to your self-hosting folder and run:
```bash
./get_latest_images apply
```
This script will:
* Check that you're authenticated with our Docker registry
* Fetch the latest Git tags from our repository
* Update your `docker-compose.yml` with the new image tags pointing to the latest builds
The script will automatically check if you're logged in. If not, it will provide you with instructions to authenticate first.
You can also check what tags are available without applying them:
```bash
./get_latest_images # Show latest tags
./get_latest_images --list # List all available tags
```
### Step 5: Restart your services
After pulling the latest images, simply restart your services:
```bash
./restart
```
That's it! Your OpenPanel instance is now running the latest and greatest version.
## Quick update workflow
Once you're set up, updating to the latest version is as simple as:
```bash
cd /path/to/self-hosting
./get_latest_images apply
./restart
```
This entire process takes less than a minute, giving you instant access to new features and fixes.
The `get_latest_images` script will:
1. Verify you're logged into the Docker registry
2. Fetch the latest tags from GitHub
3. Update your `docker-compose.yml` with images pointing to the latest commit SHAs
4. Create a backup of your docker-compose file before making changes
## Important notes
* **Stability**: While these images are tested, they may occasionally contain bugs that haven't been discovered yet. We recommend having a backup strategy.
* **Breaking changes**: We strive to maintain backwards compatibility, but occasionally breaking changes may occur. Always check the [changelog](/docs/self-hosting/changelog) before updating.
* **Support**: As a supporter, you have priority access to support. If you encounter any issues, reach out to us on Discord or via email.
## Need help?
If you have any questions or run into issues:
* Join our [Discord community](https://go.openpanel.dev/discord) (supporters get a special role!)
* Email us at [hello@openpanel.dev](mailto:hello@openpanel.dev)
* Check our [GitHub repository](https://github.com/Openpanel-dev/openpanel)
***
## Your impact
Every contribution helps us:
* Dedicate more time to development
* Maintain and improve infrastructure
* Provide better documentation and support
* Keep OpenPanel open-source and accessible for everyone
Thank you for being an essential part of OpenPanel's journey. We couldn't do this without supporters like you! π
---
## Session Replay
URL: https://openpanel.dev/docs/session-replay
import { Callout } from 'fumadocs-ui/components/callout';
Session replay captures a structured recording of what users do in your app or website. You can replay any session to see which elements were clicked, how forms were filled, and where users ran into frictionβwithout guessing.
Session replay is **not enabled by default**. You explicitly opt in per-project. When disabled, the replay script is never downloaded, keeping your analytics bundle lean.
## How it works
OpenPanel session replay is built on [rrweb](https://www.rrweb.io/), an open-source library for recording and replaying web sessions. It captures DOM mutations, mouse movements, scroll positions, and interactions as structured dataβnot video.
The replay module is loaded **asynchronously** as a separate script (`op1-replay.js`). This means:
* Your main tracking script (`op1.js`) stays lightweight even when replay is disabled
* The replay module is only downloaded for sessions that are actually recorded
* No impact on page load performance when replay is turned off
## Limits & retention
* **Unlimited replays** β no cap on the number of sessions recorded
* **30-day retention** β replays are stored and accessible for 30 days
## Setup
### Script tag
Add `sessionReplay` to your `init` call. The replay script loads automatically from the same CDN as the main script.
```html title="index.html"
```
### NPM package
```ts title="op.ts"
import { OpenPanel } from '@openpanel/web';
const op = new OpenPanel({
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
sessionReplay: {
enabled: true,
},
});
```
With the npm package, the replay module is a dynamic import code-split by your bundler. It is never included in your main bundle when session replay is disabled.
## Options
| Option | Type | Default | Description |
| -------------------- | --------- | ------------------------------- | ------------------------------------------------------------------------------------ |
| `enabled` | `boolean` | `false` | Enable session replay recording |
| `maskAllInputs` | `boolean` | `true` | Mask all input field values |
| `maskAllText` | `boolean` | `true` | Mask all text content in the recording |
| `unmaskTextSelector` | `string` | β | CSS selector for elements whose text should NOT be masked when `maskAllText` is true |
| `blockSelector` | `string` | `[data-openpanel-replay-block]` | CSS selector for elements to replace with a placeholder |
| `blockClass` | `string` | β | Class name that blocks elements from being recorded |
| `ignoreSelector` | `string` | β | CSS selector for elements excluded from interaction tracking |
| `flushIntervalMs` | `number` | `10000` | How often (ms) recorded events are sent to the server |
| `maxEventsPerChunk` | `number` | `200` | Maximum number of events per payload chunk |
| `maxPayloadBytes` | `number` | `1048576` | Maximum payload size in bytes (1 MB) |
| `scriptUrl` | `string` | β | Custom URL for the replay script (script-tag builds only) |
## Privacy controls
Session replay captures user interactions. All text and inputs are masked by default β sensitive content is replaced with `***` before it ever leaves the browser.
### Text masking (default on)
All text content is masked by default (`maskAllText: true`). This means visible page text, labels, and content are replaced with `***` in replays, in addition to input fields.
This is the safest default for GDPR compliance since replays cannot incidentally capture names, emails, or other personal data visible on the page.
### Selectively unmasking text
If your pages display non-sensitive content you want visible in replays, use `unmaskTextSelector` to opt specific elements out of masking:
```ts
sessionReplay: {
enabled: true,
unmaskTextSelector: '[data-openpanel-unmask]',
}
```
```html
Product Analytics
Welcome to the dashboard
John Doe Β· john@example.com
```
You can also use any CSS selector to target elements by class, tag, or attribute:
```ts
sessionReplay: {
enabled: true,
unmaskTextSelector: '.replay-safe, nav, footer',
}
```
### Disabling full text masking
If you want to disable full text masking and return to selector-based masking, set `maskAllText: false`. In this mode only elements with `data-openpanel-replay-mask` are masked:
```ts
sessionReplay: {
enabled: true,
maskAllText: false,
}
```
```html
This will be masked
This will be visible in replays
```
Only disable `maskAllText` if you are confident your pages do not display personal data, or if you are masking all sensitive elements individually. You are responsible for ensuring your use of session replay complies with applicable privacy law.
### Blocking elements
Elements matched by `blockSelector` or `blockClass` are replaced with a same-size grey placeholder in the replay. The element and all its children are never recorded.
```html
This section won't appear in replays at all
```
Or with a custom selector:
```ts
sessionReplay: {
enabled: true,
blockSelector: '.payment-form, .user-avatar',
blockClass: 'no-replay',
}
```
### Ignoring interactions
Use `ignoreSelector` to exclude specific elements from interaction tracking. The element remains visible in the replay but clicks and input events on it are not recorded.
```ts
sessionReplay: {
enabled: true,
ignoreSelector: '.debug-panel',
}
```
## Self-hosting
If you self-host OpenPanel, the replay script is served from your instance automatically. You can also override the script URL if you host it separately:
```ts
sessionReplay: {
enabled: true,
scriptUrl: 'https://your-cdn.example.com/op1-replay.js',
}
```
## Related
* [Session tracking](/features/session-tracking) β understand sessions without full replay
* [Session replay feature overview](/features/session-replay) β what you get with session replay
* [Web SDK](/docs/sdks/web) β full web SDK reference
* [Script tag](/docs/sdks/script) β using OpenPanel via a script tag