# OpenPanel – Full documentation for LLMs
OpenPanel is an open-source web and product analytics platform, an open-source alternative to Mixpanel with optional self-hosting.
This file contains the full text of all documentation pages. Each section is separated by --- and includes a canonical URL.
---
## What is OpenPanel?
URL: https://openpanel.dev/docs
import { UserIcon,HardDriveIcon } from 'lucide-react'
## ✨ Key Features
* **🔍 Advanced Analytics**: [Funnels](/features/funnels), cohorts, user profiles, and session history
* **📊 Real-time Dashboards**: Live data updates and interactive charts
* **🎯 A/B Testing**: Built-in variant testing with detailed breakdowns
* **🔔 Smart Notifications**: Event and funnel-based alerts
* **🌍 Privacy-First**: Cookieless tracking and GDPR compliance
* **🚀 Developer-Friendly**: Comprehensive SDKs and API access
* **📦 Self-Hosted**: Full control over your data and infrastructure
* **💸 Transparent Pricing**: No hidden costs
* **🛠️ Custom Dashboards**: Flexible chart creation and [data visualization](/features/data-visualization)
* **📱 Multi-Platform**: Web, mobile (iOS/Android), and server-side tracking
## 📊 Analytics Platform Comparison
| Feature | OpenPanel | Mixpanel | GA4 | Plausible |
| ----------------------------------------- | --------- | -------- | ----- | --------- |
| ✅ Open-source | ✅ | ❌ | ❌ | ✅ |
| 🧩 Self-hosting supported | ✅ | ❌ | ❌ | ✅ |
| 🔒 Cookieless by default | ✅ | ❌ | ❌ | ✅ |
| 🔁 Real-time dashboards | ✅ | ✅ | ❌ | ✅ |
| 🔍 Funnels & cohort analysis | ✅ | ✅ | ✅\* | ✅\*\*\* |
| 👤 User profiles & session history | ✅ | ✅ | ❌ | ❌ |
| 📈 Custom dashboards & charts | ✅ | ✅ | ✅ | ❌ |
| 💬 Event & funnel notifications | ✅ | ✅ | ❌ | ❌ |
| 🌍 GDPR-compliant tracking | ✅ | ✅ | ❌\*\* | ✅ |
| 📦 SDKs (Web, Swift, Kotlin, ReactNative) | ✅ | ✅ | ✅ | ❌ |
| 💸 Transparent pricing | ✅ | ❌ | ✅\* | ✅ |
| 🚀 Built for developers | ✅ | ✅ | ❌ | ✅ |
| 🔧 A/B testing & variant breakdowns | ✅ | ✅ | ❌ | ❌ |
✅\* GA4 has a free tier but often requires BigQuery (paid) for raw data access.\
❌\*\* GA4 has faced GDPR bans in several EU countries due to data transfers to US-based servers.\
✅\*\*\* Plausible has simple goals
## 🚀 Quick Start
Before you can start tracking your events you'll need to create an account or spin up your own instance of OpenPanel.
} description="Create your account and workspace" />
} description="Get full control and start self-host" />
1. **[Install OpenPanel](/docs/get-started/install-openpanel)** - Add the script tag or use one of our SDKs
2. **[Track Events](/docs/get-started/track-events)** - Start measuring user actions
3. **[Identify Users](/docs/get-started/identify-users)** - Connect events to specific users
4. **[Track Revenue](/docs/revenue-tracking)** - Monitor purchases and subscriptions
## 🔒 Privacy First
OpenPanel is built with privacy in mind:
* **No cookies required** - Cookieless tracking by default
* **GDPR and CCPA compliant** - Built for privacy regulations
* **Self-hosting option** - Full control over your data
* **Transparent data handling** - You own your data
## 🌐 Open Source
OpenPanel is fully open-source and available on [GitHub](https://github.com/Openpanel-dev/openpanel). We believe in transparency and community-driven development.
## 💬 Need Help?
* Join our [Discord community](https://go.openpanel.dev/discord)
* Check our [GitHub issues](https://github.com/Openpanel-dev/openpanel/issues)
* Email us at [hello@openpanel.dev](mailto:hello@openpanel.dev)
---
## Avoid adblockers with proxy
URL: https://openpanel.dev/docs/adblockers
In this article we need to talk about adblockers, why they exist, how they work, and how to avoid them.
Adblockers' main purpose was initially to block ads, but they have since started to block tracking scripts as well. This is primarily for privacy reasons, and while we respect that, there are legitimate use cases for understanding your visitors. OpenPanel is designed to be a privacy-friendly, cookieless analytics tool that doesn't track users across sites, but generic blocklists often catch all analytics tools indiscriminately.
The best way to avoid adblockers is to proxy events via your own domain name. Adblockers generally cannot block requests to your own domain (first-party requests) without breaking the functionality of the site itself.
## Built-in Support
Today, our Next.js SDK and WordPress plugin have built-in support for proxying:
* **WordPress**: Does it automatically.
* **Next.js**: Easy to setup with a route handler.
## Implementing Proxying for Any Framework
If you are not using Next.js or WordPress, you can implement proxying in any backend framework. The key is to set up an API endpoint on your domain (e.g., `api.domain.com` or `domain.com/api`) that forwards requests to OpenPanel.
Below is an example of how to set up a proxy using a [Hono](https://hono.dev/) server. This implementation mimics the logic used in our Next.js SDK.
> You can always see how our Next.js implementation looks like in our [repository](https://github.com/Openpanel-dev/openpanel/blob/main/packages/sdks/nextjs/createNextRouteHandler.ts).
### Hono Example
```typescript
import { Hono } from 'hono'
const app = new Hono()
// 1. Proxy the script file
app.get('/op1.js', async (c) => {
const scriptUrl = 'https://openpanel.dev/op1.js'
try {
const res = await fetch(scriptUrl)
const text = await res.text()
c.header('Content-Type', 'text/javascript')
// Optional caching for 24 hours
c.header('Cache-Control', 'public, max-age=86400, stale-while-revalidate=86400')
return c.body(text)
} catch (e) {
return c.json({ error: 'Failed to fetch script' }, 500)
}
})
// 2. Proxy the track event
app.post('/track', async (c) => {
const body = await c.req.json()
// Forward the client's IP address (be sure to pick correct IP based on your infra)
const ip = c.req.header('cf-connecting-ip') ??
c.req.header('x-forwarded-for')?.split(',')[0]
const headers = new Headers()
headers.set('Content-Type', 'application/json')
headers.set('Origin', c.req.header('origin') ?? '')
headers.set('User-Agent', c.req.header('user-agent') ?? '')
headers.set('openpanel-client-id', c.req.header('openpanel-client-id') ?? '')
if (ip) {
headers.set('openpanel-client-ip', ip)
}
try {
const res = await fetch('https://api.openpanel.dev/track', {
method: 'POST',
headers,
body: JSON.stringify(body),
})
return c.json(await res.text(), res.status)
} catch (e) {
return c.json(e, 500)
}
})
export default app
```
This script sets up two endpoints:
1. `GET /op1.js`: Fetches the OpenPanel script and serves it from your domain.
2. `POST /track`: Receives events from the frontend, adds necessary headers (User-Agent, Origin, Content-Type, openpanel-client-id, openpanel-client-ip), and forwards them to OpenPanel's API.
## Frontend Configuration
Once your proxy is running, you need to configure the OpenPanel script on your frontend to use your proxy endpoints instead of the default ones.
```html
```
By doing this, all requests are sent to your domain first, bypassing adblockers that look for third-party tracking domains.
---
## Authentication
URL: https://openpanel.dev/docs/api/authentication
## Authentication
To authenticate with the OpenPanel API, you need to use your `clientId` and `clientSecret`. Different API endpoints may require different access levels:
* **Track API**: Default client works with `write` mode
* **Export API**: Requires `read` or `root` mode
* **Insights API**: Requires `read` or `root` mode
* **Manage API**: Requires `root` mode only
The default client (created with a project) has `write` mode and does not have access to the Export, Insights, or Manage APIs. You'll need to create additional clients with appropriate access levels.
## Headers
Include the following headers with your API requests:
* `openpanel-client-id`: Your OpenPanel client ID
* `openpanel-client-secret`: Your OpenPanel client secret
## Example
```bash
curl 'https://api.openpanel.dev/insights/{projectId}/metrics' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
## Security Best Practices
1. **Store credentials securely**: Never expose your `clientId` and `clientSecret` in client-side code
2. **Use HTTPS**: Always use HTTPS to ensure secure communication
3. **Rotate credentials**: Regularly rotate your API credentials
4. **Limit access**: Use the minimum required access level for your use case
## Error Responses
If authentication fails, you'll receive a `401 Unauthorized` response:
```json
{
"error": "Unauthorized",
"message": "Invalid client credentials"
}
```
Common authentication errors:
* Invalid client ID or secret
* Client doesn't have required permissions (e.g., trying to access Manage API with a non-root client)
* Malformed client ID (must be a valid UUIDv4)
* Client type mismatch (e.g., `write` client trying to access Export API)
## Client Types
OpenPanel supports three client types with different access levels:
| Type | Description | Access |
| ------- | ---------------- | ----------------------------- |
| `write` | Write access | Track API only |
| `read` | Read-only access | Export API, Insights API |
| `root` | Full access | All APIs including Manage API |
**Note**: Root clients have organization-wide access and can manage all resources. Use root clients carefully and store their credentials securely.
## Rate Limiting
The API implements rate limiting to prevent abuse. Rate limits vary by endpoint:
* **Track API**: Higher limits for [event tracking](/features/event-tracking)
* **Export/Insights APIs**: 100 requests per 10 seconds
* **Manage API**: 20 requests per 10 seconds
If you exceed the rate limit, you'll receive a `429 Too Many Requests` response. Implement exponential backoff for retries.
Remember to replace `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` with your actual OpenPanel API credentials.
---
## Export
URL: https://openpanel.dev/docs/api/export
## Authentication
To authenticate with the Export API, you need to use your `clientId` and `clientSecret`. Make sure your client has `read` or `root` mode. The default client does not have access to the Export API.
For detailed authentication information, see the [Authentication](/docs/api/authentication) guide.
Include the following headers with your requests:
* `openpanel-client-id`: Your OpenPanel client ID
* `openpanel-client-secret`: Your OpenPanel client secret
## Base URL
All Export API requests should be made to:
```
https://api.openpanel.dev/export
```
## Common Query Parameters
Most endpoints support the following query parameters:
| Parameter | Type | Description | Default |
| ----------- | ------ | -------------------------------------------------- | -------------- |
| `projectId` | string | The ID of the project (alternative: `project_id`) | Required |
| `startDate` | string | Start date (ISO format: YYYY-MM-DD) | Based on range |
| `endDate` | string | End date (ISO format: YYYY-MM-DD) | Based on range |
| `range` | string | Predefined date range (`7d`, `30d`, `today`, etc.) | None |
## Endpoints
### Get Events
Retrieve individual events from a specific project within a date range. This endpoint provides raw event data with optional filtering and pagination.
```
GET /export/events
```
#### Query Parameters
| Parameter | Type | Description | Example |
| ----------- | ------------------- | -------------------------------------------------- | ------------------------------------------------- |
| `projectId` | string | The ID of the project to fetch events from | `abc123` |
| `profileId` | string | Filter events by specific profile/user ID | `user_123` |
| `event` | string or string\[] | Event name(s) to filter | `screen_view` or `["screen_view","button_click"]` |
| `start` | string | Start date for the event range (ISO format) | `2024-04-15` |
| `end` | string | End date for the event range (ISO format) | `2024-04-18` |
| `page` | number | Page number for pagination (default: 1) | `2` |
| `limit` | number | Number of events per page (default: 50, max: 1000) | `100` |
| `includes` | string or string\[] | Additional fields to include in the response | `profile` or `["profile","meta"]` |
#### Include Options
The `includes` parameter allows you to fetch additional related data:
* `profile`: Include user profile information
* `meta`: Include event metadata and configuration
#### Example Request
```bash
curl 'https://api.openpanel.dev/export/events?projectId=abc123&event=screen_view&start=2024-04-15&end=2024-04-18&page=1&limit=100&includes=profile,meta' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
{
"meta": {
"count": 50,
"totalCount": 1250,
"pages": 25,
"current": 1
},
"data": [
{
"id": "evt_123456789",
"name": "screen_view",
"deviceId": "device_abc123",
"profileId": "user_789",
"projectId": "abc123",
"sessionId": "session_xyz",
"properties": {
"path": "/dashboard",
"title": "Dashboard",
"url": "https://example.com/dashboard"
},
"createdAt": "2024-04-15T10:30:00.000Z",
"country": "United States",
"city": "New York",
"region": "New York",
"os": "macOS",
"browser": "Chrome",
"device": "Desktop",
"duration": 0,
"path": "/dashboard",
"origin": "https://example.com",
"profile": {
"id": "user_789",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"isExternal": true,
"createdAt": "2024-04-01T08:00:00.000Z"
},
"meta": {
"name": "screen_view",
"description": "Page view tracking",
"conversion": false
}
}
]
}
```
### Get Charts
Retrieve aggregated chart data for analytics and visualization. This endpoint provides time-series data with advanced filtering, breakdowns, and comparison capabilities.
```
GET /export/charts
```
#### Query Parameters
| Parameter | Type | Description | Example |
| ------------ | --------- | ---------------------------------------------------- | --------------------------------------- |
| `projectId` | string | The ID of the project to fetch chart data from | `abc123` |
| `events` | object\[] | Array of event configurations to analyze | `[{"name":"screen_view","filters":[]}]` |
| `breakdowns` | object\[] | Array of breakdown dimensions | `[{"name":"country"}]` |
| `interval` | string | Time interval for data points | `day` |
| `range` | string | Predefined date range | `7d` |
| `previous` | boolean | Include data from the previous period for comparison | `true` |
| `startDate` | string | Custom start date (ISO format) | `2024-04-01` |
| `endDate` | string | Custom end date (ISO format) | `2024-04-30` |
#### Event Configuration
Each event in the `events` array supports the following properties:
| Property | Type | Description | Required | Default |
| ---------- | --------- | ----------------------------------------- | -------- | ------- |
| `name` | string | Name of the event to track | Yes | - |
| `filters` | Filter\[] | Array of filters to apply to the event | No | `[]` |
| `segment` | string | Type of segmentation | No | `event` |
| `property` | string | Property name for property-based segments | No | - |
#### Segmentation Options
* `event`: Count individual events (default)
* `user`: Count unique users/profiles
* `session`: Count unique sessions
* `user_average`: Average events per user
* `one_event_per_user`: One event per user (deduplicated)
* `property_sum`: Sum of a numeric property
* `property_average`: Average of a numeric property
* `property_min`: Minimum value of a numeric property
* `property_max`: Maximum value of a numeric property
#### Filter Configuration
Each filter in the `filters` array supports:
| Property | Type | Description | Required |
| ---------- | ------ | ---------------------------------- | -------- |
| `name` | string | Property name to filter on | Yes |
| `operator` | string | Comparison operator | Yes |
| `value` | array | Array of values to compare against | Yes |
#### Filter Operators
* `is`: Exact match
* `isNot`: Not equal to
* `contains`: Contains substring
* `doesNotContain`: Does not contain substring
* `startsWith`: Starts with
* `endsWith`: Ends with
* `regex`: Regular expression match
* `isNull`: Property is null or empty
* `isNotNull`: Property has a value
#### Breakdown Dimensions
Common breakdown dimensions include:
| Dimension | Description | Example Values |
| ---------- | ------------------- | ----------------------------- |
| `country` | User's country | `United States`, `Canada` |
| `region` | User's region/state | `California`, `New York` |
| `city` | User's city | `San Francisco`, `New York` |
| `device` | Device type | `Desktop`, `Mobile`, `Tablet` |
| `browser` | Browser name | `Chrome`, `Firefox`, `Safari` |
| `os` | Operating system | `macOS`, `Windows`, `iOS` |
| `referrer` | Referrer URL | `google.com`, `facebook.com` |
| `path` | Page path | `/`, `/dashboard`, `/pricing` |
#### Time Intervals
* `minute`: Minute-by-minute data
* `hour`: Hourly aggregation
* `day`: Daily aggregation (default)
* `week`: Weekly aggregation
* `month`: Monthly aggregation
#### Date Ranges
* `30min`: Last 30 minutes
* `lastHour`: Last hour
* `today`: Current day
* `yesterday`: Previous day
* `7d`: Last 7 days
* `30d`: Last 30 days
* `6m`: Last 6 months
* `12m`: Last 12 months
* `monthToDate`: Current month to date
* `lastMonth`: Previous month
* `yearToDate`: Current year to date
* `lastYear`: Previous year
#### Example Request
```bash
curl 'https://api.openpanel.dev/export/charts?projectId=abc123&events=[{"name":"screen_view","segment":"user"}]&breakdowns=[{"name":"country"}]&interval=day&range=30d&previous=true' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Example Advanced Request
```bash
curl 'https://api.openpanel.dev/export/charts' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET' \
-G \
--data-urlencode 'projectId=abc123' \
--data-urlencode 'events=[{"name":"purchase","segment":"property_sum","property":"properties.total","filters":[{"name":"properties.total","operator":"isNotNull","value":[]}]}]' \
--data-urlencode 'breakdowns=[{"name":"country"}]' \
--data-urlencode 'interval=day' \
--data-urlencode 'range=30d'
```
#### Response
```json
{
"series": [
{
"id": "screen_view-united-states",
"names": ["screen_view", "United States"],
"event": {
"id": "evt1",
"name": "screen_view"
},
"metrics": {
"sum": 1250,
"average": 41.67,
"min": 12,
"max": 89,
"previous": {
"sum": {
"value": 1100,
"change": 13.64
},
"average": {
"value": 36.67,
"change": 13.64
}
}
},
"data": [
{
"date": "2024-04-01T00:00:00.000Z",
"count": 45,
"previous": {
"value": 38,
"change": 18.42
}
},
{
"date": "2024-04-02T00:00:00.000Z",
"count": 52,
"previous": {
"value": 41,
"change": 26.83
}
}
]
}
],
"metrics": {
"sum": 1250,
"average": 41.67,
"min": 12,
"max": 89,
"previous": {
"sum": {
"value": 1100,
"change": 13.64
}
}
}
}
```
## Error Handling
The API uses standard HTTP response codes. Common error responses:
### 400 Bad Request
```json
{
"error": "Bad Request",
"message": "Invalid query parameters",
"details": [
{
"path": ["events", 0, "name"],
"message": "Required"
}
]
}
```
### 401 Unauthorized
```json
{
"error": "Unauthorized",
"message": "Invalid client credentials"
}
```
### 403 Forbidden
```json
{
"error": "Forbidden",
"message": "You do not have access to this project"
}
```
### 404 Not Found
```json
{
"error": "Not Found",
"message": "Project not found"
}
```
### 429 Too Many Requests
Rate limiting response includes headers indicating your rate limit status.
## Rate Limiting
The Export API implements rate limiting:
* **100 requests per 10 seconds** per client
* Rate limit headers included in responses
* Implement exponential backoff for retries
## Data Types and Formats
### Event Properties
Event properties are stored as key-value pairs and can include:
* **Built-in properties**: `path`, `origin`, `title`, `url`, `hash`
* **UTM parameters**: `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`
* **Custom properties**: Any custom data you track with your events
### Property Access
Properties can be accessed in filters and breakdowns using dot notation:
* `properties.custom_field`: Access custom properties
* `profile.properties.user_type`: Access profile properties
* `properties.__query.utm_source`: Access query parameters
### Date Handling
* All dates are in ISO 8601 format
* Timezone handling is done server-side based on project settings
* Date ranges are inclusive of start and end dates
### Geographic Data
Geographic information is automatically collected when available:
* `country`: Full country name
* `region`: State/province/region
* `city`: City name
* `longitude`/`latitude`: Coordinates (when available)
### Device Information
Device data is collected from user agents:
* `device`: Device type (Desktop, Mobile, Tablet)
* `browser`: Browser name and version
* `os`: Operating system and version
* `brand`/`model`: Device brand and model (mobile devices)
## Notes
* Event data is typically available within seconds of tracking
* All timezone handling is done server-side based on project settings
* Property names are case-sensitive in filters and breakdowns
Remember to replace `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` with your actual OpenPanel API credentials.
---
## Insights
URL: https://openpanel.dev/docs/api/insights
## Authentication
To authenticate with the Insights API, you need to use your `clientId` and `clientSecret`. Make sure your client has `read` or `root` mode. The default client does not have access to the Insights API.
For detailed authentication information, see the [Authentication](/docs/api/authentication) guide.
Include the following headers with your requests:
* `openpanel-client-id`: Your OpenPanel client ID
* `openpanel-client-secret`: Your OpenPanel client secret
## Base URL
All Insights API requests should be made to:
```
https://api.openpanel.dev/insights
```
## Common Query Parameters
Most endpoints support the following query parameters:
| Parameter | Type | Description | Default |
| ----------- | ------ | ------------------------------------------------ | -------------- |
| `startDate` | string | Start date (ISO format: YYYY-MM-DD) | Based on range |
| `endDate` | string | End date (ISO format: YYYY-MM-DD) | Based on range |
| `range` | string | Predefined date range (`7d`, `30d`, `90d`, etc.) | `7d` |
| `filters` | array | Event filters to apply | `[]` |
| `cursor` | number | Page number for pagination | `1` |
| `limit` | number | Number of results per page (max: 50) | `10` |
### Filter Configuration
Filters can be applied to narrow down results. Each filter has the following structure:
```json
{
"name": "property_name",
"operator": "is|isNot|contains|doesNotContain|startsWith|endsWith|regex",
"value": ["value1", "value2"]
}
```
## Endpoints
### Get Metrics
Retrieve comprehensive website metrics including visitors, sessions, page views, and engagement data.
```
GET /insights/{projectId}/metrics
```
#### Query Parameters
| Parameter | Type | Description | Example |
| ----------- | ------ | ---------------------- | ----------------------------------------------------- |
| `startDate` | string | Start date for metrics | `2024-01-01` |
| `endDate` | string | End date for metrics | `2024-01-31` |
| `range` | string | Predefined range | `7d` |
| `filters` | array | Event filters | `[{"name":"path","operator":"is","value":["/home"]}]` |
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/metrics?range=30d&filters=[{"name":"path","operator":"contains","value":["/product"]}]' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
{
"metrics": {
"bounce_rate": 45.2,
"unique_visitors": 1250,
"total_sessions": 1580,
"avg_session_duration": 185.5,
"total_screen_views": 4230,
"views_per_session": 2.67
},
"series": [
{
"date": "2024-01-01T00:00:00.000Z",
"bounce_rate": 42.1,
"unique_visitors": 85,
"total_sessions": 98,
"avg_session_duration": 195.2,
"total_screen_views": 156,
"views_per_session": 1.59
}
]
}
```
### Get Live Visitors
Get the current number of active visitors on your website in real-time.
```
GET /insights/{projectId}/live
```
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/live' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
{
"visitors": 23
}
```
### Get Top Pages
Retrieve the most visited pages with detailed analytics including session count, bounce rate, and average time on page.
```
GET /insights/{projectId}/pages
```
#### Query Parameters
| Parameter | Type | Description | Example |
| ----------- | ------ | -------------------------- | ------------ |
| `startDate` | string | Start date | `2024-01-01` |
| `endDate` | string | End date | `2024-01-31` |
| `range` | string | Predefined range | `7d` |
| `filters` | array | Event filters | `[]` |
| `cursor` | number | Page number | `1` |
| `limit` | number | Results per page (max: 50) | `10` |
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/pages?range=7d&limit=20' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
[
{
"title": "Homepage - Example Site",
"origin": "https://example.com",
"path": "/",
"sessions": 456,
"bounce_rate": 35.2,
"avg_duration": 125.8
},
{
"title": "About Us",
"origin": "https://example.com",
"path": "/about",
"sessions": 234,
"bounce_rate": 45.1,
"avg_duration": 89.3
}
]
```
### Get Referrer Data
Retrieve referrer analytics to understand where your traffic is coming from.
```
GET /insights/{projectId}/referrer
GET /insights/{projectId}/referrer_name
GET /insights/{projectId}/referrer_type
```
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/referrer?range=30d&limit=15' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
[
{
"name": "google.com",
"sessions": 567,
"bounce_rate": 42.1,
"avg_session_duration": 156.7
},
{
"name": "facebook.com",
"sessions": 234,
"bounce_rate": 38.9,
"avg_session_duration": 189.2
}
]
```
### Get UTM Campaign Data
Analyze your marketing campaigns with UTM parameter breakdowns.
```
GET /insights/{projectId}/utm_source
GET /insights/{projectId}/utm_medium
GET /insights/{projectId}/utm_campaign
GET /insights/{projectId}/utm_term
GET /insights/{projectId}/utm_content
```
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/utm_source?range=30d' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
[
{
"name": "google",
"sessions": 890,
"bounce_rate": 35.4,
"avg_session_duration": 178.9
},
{
"name": "facebook",
"sessions": 456,
"bounce_rate": 41.2,
"avg_session_duration": 142.3
}
]
```
### Get Geographic Data
Understand your audience location with country, region, and city breakdowns.
```
GET /insights/{projectId}/country
GET /insights/{projectId}/region
GET /insights/{projectId}/city
```
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/country?range=30d&limit=20' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
[
{
"name": "United States",
"sessions": 1234,
"bounce_rate": 38.7,
"avg_session_duration": 167.4
},
{
"name": "United Kingdom",
"sessions": 567,
"bounce_rate": 42.1,
"avg_session_duration": 145.8
}
]
```
For region and city endpoints, an additional `prefix` field may be included:
```json
[
{
"prefix": "United States",
"name": "California",
"sessions": 456,
"bounce_rate": 35.2,
"avg_session_duration": 172.1
}
]
```
### Get Device & Technology Data
Analyze visitor devices, browsers, and operating systems.
```
GET /insights/{projectId}/device
GET /insights/{projectId}/browser
GET /insights/{projectId}/browser_version
GET /insights/{projectId}/os
GET /insights/{projectId}/os_version
GET /insights/{projectId}/brand
GET /insights/{projectId}/model
```
#### Example Request
```bash
curl 'https://api.openpanel.dev/insights/abc123/browser?range=7d' \
-H 'openpanel-client-id: YOUR_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_CLIENT_SECRET'
```
#### Response
```json
[
{
"name": "Chrome",
"sessions": 789,
"bounce_rate": 36.4,
"avg_session_duration": 162.3
},
{
"name": "Firefox",
"sessions": 234,
"bounce_rate": 41.7,
"avg_session_duration": 148.9
}
]
```
For version-specific endpoints (browser\_version, os\_version), a `prefix` field shows the parent:
```json
[
{
"prefix": "Chrome",
"name": "118.0.0.0",
"sessions": 456,
"bounce_rate": 35.8,
"avg_session_duration": 165.7
}
]
```
## Error Handling
The API uses standard HTTP response codes. Common error responses:
### 400 Bad Request
```json
{
"error": "Bad Request",
"message": "Invalid query parameters",
"details": {
"issues": [
{
"path": ["range"],
"message": "Invalid enum value"
}
]
}
}
```
### 401 Unauthorized
```json
{
"error": "Unauthorized",
"message": "Invalid client credentials"
}
```
### 429 Too Many Requests
Rate limiting response includes headers indicating your rate limit status.
## Rate Limiting
The Insights API implements rate limiting:
* **100 requests per 10 seconds** per client
* Rate limit headers included in responses
* Implement exponential backoff for retries
## Notes
* All dates are returned in ISO 8601 format
* Durations are in seconds
* Bounce rates and percentages are returned as decimal numbers (e.g., 45.2 = 45.2%)
* Session duration is the average time spent on the website
* All timezone handling is done server-side based on project settings
---
## Manage API Overview
URL: https://openpanel.dev/docs/api/manage
## Overview
The Manage API provides programmatic access to manage your OpenPanel resources including projects, clients, and references. This API is designed for automation, infrastructure-as-code, and administrative tasks.
## Authentication
The Manage API requires a **root client** for authentication. Root clients have organization-wide access and can manage all resources within their organization.
To authenticate with the Manage API, you need:
* A client with `type: 'root'`
* Your `clientId` and `clientSecret`
For detailed authentication information, see the [Authentication](/docs/api/authentication) guide.
Include the following headers with your requests:
* `openpanel-client-id`: Your OpenPanel root client ID
* `openpanel-client-secret`: Your OpenPanel root client secret
## Base URL
All Manage API requests should be made to:
```
https://api.openpanel.dev/manage
```
## Available Resources
The Manage API provides CRUD operations for three resource types:
### Projects
Manage your analytics projects programmatically:
* **[Projects Documentation](/docs/api/manage/projects)** - Create, read, update, and delete projects
* Automatically creates a default write client when creating a project
* Supports project configuration including domains, CORS settings, and project types
### Clients
Manage API clients for your projects:
* **[Clients Documentation](/docs/api/manage/clients)** - Create, read, update, and delete clients
* Supports different client types: `read`, `write`, and `root`
* Auto-generates secure secrets on creation (returned once)
### References
Manage reference points for your analytics:
* **[References Documentation](/docs/api/manage/references)** - Create, read, update, and delete references
* Useful for marking important dates or events in your analytics timeline
* Can be filtered by project
## Common Features
All endpoints share these common characteristics:
### Organization Scope
All operations are scoped to your organization. You can only manage resources that belong to your organization.
### Response Format
Successful responses follow this structure:
```json
{
"data": {
// Resource data
}
}
```
For list endpoints:
```json
{
"data": [
// Array of resources
]
}
```
### Error Handling
The API uses standard HTTP response codes:
* `200 OK` - Request successful
* `400 Bad Request` - Invalid request parameters
* `401 Unauthorized` - Authentication failed
* `404 Not Found` - Resource not found
* `429 Too Many Requests` - Rate limit exceeded
## Rate Limiting
The Manage API implements rate limiting:
* **20 requests per 10 seconds** per client
* Rate limit headers included in responses
* Implement exponential backoff for retries
## Use Cases
The Manage API is ideal for:
* **Infrastructure as Code**: Manage OpenPanel resources alongside your application infrastructure
* **Automation**: Automatically create projects and clients for new deployments
* **Bulk Operations**: Programmatically manage multiple resources
* **CI/CD Integration**: Set up projects and clients as part of your deployment pipeline
* **Administrative Tools**: Build custom admin interfaces
## Security Best Practices
1. **Root Clients Only**: Only root clients can access the Manage API
2. **Store Credentials Securely**: Never expose root client credentials in client-side code
3. **Use HTTPS**: Always use HTTPS for API requests
4. **Rotate Credentials**: Regularly rotate your root client credentials
5. **Limit Access**: Restrict root client creation to trusted administrators
## Getting Started
1. **Create a Root Client**: Use the dashboard to create a root client in your organization
2. **Store Credentials**: Securely store your root client ID and secret
3. **Make Your First Request**: Start with listing projects to verify authentication
Example:
```bash
curl 'https://api.openpanel.dev/manage/projects' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
## Next Steps
* Read the [Projects documentation](/docs/api/manage/projects) to manage projects
* Read the [Clients documentation](/docs/api/manage/clients) to manage API clients
* Read the [References documentation](/docs/api/manage/references) to manage reference points
---
## Clients
URL: https://openpanel.dev/docs/api/manage/clients
## Authentication
To authenticate with the Clients API, you need to use your `clientId` and `clientSecret` from a root client. Root clients have organization-wide access.
For detailed authentication information, see the [Authentication](/docs/api/authentication) guide.
Include the following headers with your requests:
* `openpanel-client-id`: Your OpenPanel root client ID
* `openpanel-client-secret`: Your OpenPanel root client secret
## Base URL
All Clients API requests should be made to:
```
https://api.openpanel.dev/manage/clients
```
## Client Types
OpenPanel supports three client types with different access levels:
| Type | Description | Use Case |
| ------- | ---------------- | ------------------------------------------------ |
| `read` | Read-only access | Export data, view insights, read-only operations |
| `write` | Write access | Track events, send data to OpenPanel |
| `root` | Full access | Manage resources, access Manage API |
**Note**: Only `root` clients can access the Manage API.
## Endpoints
### List Clients
Retrieve all clients in your organization, optionally filtered by project.
```
GET /manage/clients
```
#### Query Parameters
| Parameter | Type | Description |
| ----------- | ------ | -------------------------------------- |
| `projectId` | string | Optional. Filter clients by project ID |
#### Example Request
```bash
# List all clients
curl 'https://api.openpanel.dev/manage/clients' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
# List clients for a specific project
curl 'https://api.openpanel.dev/manage/clients?projectId=my-project' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"data": [
{
"id": "fa0c2780-55f2-4d9e-bea0-da2e02c7b1a9",
"name": "First client",
"type": "write",
"projectId": "my-project",
"organizationId": "org_123",
"ignoreCorsAndSecret": false,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
},
{
"id": "b8904453-863d-4e04-8ebc-8abae30ffb1a",
"name": "Read-only Client",
"type": "read",
"projectId": "my-project",
"organizationId": "org_123",
"ignoreCorsAndSecret": false,
"createdAt": "2024-01-15T11:00:00.000Z",
"updatedAt": "2024-01-15T11:00:00.000Z"
}
]
}
```
**Note**: Client secrets are never returned in list or get responses for security reasons.
### Get Client
Retrieve a specific client by ID.
```
GET /manage/clients/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------------- |
| `id` | string | The ID of the client (UUID) |
#### Example Request
```bash
curl 'https://api.openpanel.dev/manage/clients/fa0c2780-55f2-4d9e-bea0-da2e02c7b1a9' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"data": {
"id": "fa0c2780-55f2-4d9e-bea0-da2e02c7b1a9",
"name": "First client",
"type": "write",
"projectId": "my-project",
"organizationId": "org_123",
"ignoreCorsAndSecret": false,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}
```
### Create Client
Create a new API client. A secure secret is automatically generated and returned once.
```
POST /manage/clients
```
#### Request Body
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------------------------------- |
| `name` | string | Yes | Client name (minimum 1 character) |
| `projectId` | string | No | Associate client with a specific project |
| `type` | string | No | Client type: `read`, `write`, or `root` (default: `write`) |
#### Example Request
```bash
curl -X POST 'https://api.openpanel.dev/manage/clients' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"name": "My API Client",
"projectId": "my-project",
"type": "read"
}'
```
#### Response
```json
{
"data": {
"id": "b8904453-863d-4e04-8ebc-8abae30ffb1a",
"name": "My API Client",
"type": "read",
"projectId": "my-project",
"organizationId": "org_123",
"ignoreCorsAndSecret": false,
"createdAt": "2024-01-15T11:00:00.000Z",
"updatedAt": "2024-01-15T11:00:00.000Z",
"secret": "sec_b2521ca283bf903b46b3"
}
}
```
**Important**: The `secret` field is only returned once when the client is created. Store it securely immediately. You cannot retrieve the secret later - if lost, you'll need to delete and recreate the client.
### Update Client
Update an existing client's name.
```
PATCH /manage/clients/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------------- |
| `id` | string | The ID of the client (UUID) |
#### Request Body
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------- |
| `name` | string | No | New client name (minimum 1 character) |
**Note**: Currently, only the `name` field can be updated. To change the client type or project association, delete and recreate the client.
#### Example Request
```bash
curl -X PATCH 'https://api.openpanel.dev/manage/clients/b8904453-863d-4e04-8ebc-8abae30ffb1a' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"name": "Updated Client Name"
}'
```
#### Response
```json
{
"data": {
"id": "b8904453-863d-4e04-8ebc-8abae30ffb1a",
"name": "Updated Client Name",
"type": "read",
"projectId": "my-project",
"organizationId": "org_123",
"ignoreCorsAndSecret": false,
"createdAt": "2024-01-15T11:00:00.000Z",
"updatedAt": "2024-01-15T11:30:00.000Z"
}
}
```
### Delete Client
Permanently delete a client. This action cannot be undone.
```
DELETE /manage/clients/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------------- |
| `id` | string | The ID of the client (UUID) |
#### Example Request
```bash
curl -X DELETE 'https://api.openpanel.dev/manage/clients/b8904453-863d-4e04-8ebc-8abae30ffb1a' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"success": true
}
```
**Warning**: Deleting a client is permanent. Any applications using this client will immediately lose access. Make sure to update your applications before deleting a client.
## Error Handling
The API uses standard HTTP response codes. Common error responses:
### 400 Bad Request
```json
{
"error": "Bad Request",
"message": "Invalid request body",
"details": [
{
"path": ["name"],
"message": "String must contain at least 1 character(s)"
}
]
}
```
### 401 Unauthorized
```json
{
"error": "Unauthorized",
"message": "Manage: Only root clients are allowed to manage resources"
}
```
### 404 Not Found
```json
{
"error": "Not Found",
"message": "Client not found"
}
```
### 429 Too Many Requests
Rate limiting response includes headers indicating your rate limit status.
## Rate Limiting
The Clients API implements rate limiting:
* **20 requests per 10 seconds** per client
* Rate limit headers included in responses
* Implement exponential backoff for retries
## Security Best Practices
1. **Store Secrets Securely**: Client secrets are only shown once on creation. Store them in secure credential management systems
2. **Use Appropriate Client Types**: Use the minimum required access level for each use case
3. **Rotate Secrets Regularly**: Delete old clients and create new ones to rotate secrets
4. **Never Expose Secrets**: Never commit client secrets to version control or expose them in client-side code
5. **Monitor Client Usage**: Regularly review and remove unused clients
## Notes
* Client IDs are UUIDs (Universally Unique Identifiers)
* Client secrets are automatically generated with the format `sec_` followed by random hex characters
* Secrets are hashed using argon2 before storage
* Clients can be associated with a project or exist at the organization level
* Clients are scoped to your organization - you can only manage clients in your organization
* The `ignoreCorsAndSecret` field is an advanced setting that bypasses CORS and secret validation (use with caution)
---
## Projects
URL: https://openpanel.dev/docs/api/manage/projects
## Authentication
To authenticate with the Projects API, you need to use your `clientId` and `clientSecret` from a root client. Root clients have organization-wide access.
For detailed authentication information, see the [Authentication](/docs/api/authentication) guide.
Include the following headers with your requests:
* `openpanel-client-id`: Your OpenPanel root client ID
* `openpanel-client-secret`: Your OpenPanel root client secret
## Base URL
All Projects API requests should be made to:
```
https://api.openpanel.dev/manage/projects
```
## Endpoints
### List Projects
Retrieve all projects in your organization.
```
GET /manage/projects
```
#### Example Request
```bash
curl 'https://api.openpanel.dev/manage/projects' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"data": [
{
"id": "my-project",
"name": "My Project",
"organizationId": "org_123",
"domain": "https://example.com",
"cors": ["https://example.com", "https://www.example.com"],
"crossDomain": false,
"allowUnsafeRevenueTracking": false,
"filters": [],
"types": ["website"],
"eventsCount": 0,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z",
"deleteAt": null
}
]
}
```
### Get Project
Retrieve a specific project by ID.
```
GET /manage/projects/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------- |
| `id` | string | The ID of the project |
#### Example Request
```bash
curl 'https://api.openpanel.dev/manage/projects/my-project' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"data": {
"id": "my-project",
"name": "My Project",
"organizationId": "org_123",
"domain": "https://example.com",
"cors": ["https://example.com"],
"crossDomain": false,
"allowUnsafeRevenueTracking": false,
"filters": [],
"types": ["website"],
"eventsCount": 0,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z",
"deleteAt": null
}
}
```
### Create Project
Create a new project in your organization. A default write client is automatically created with the project.
```
POST /manage/projects
```
#### Request Body
| Parameter | Type | Required | Description |
| ------------- | -------------- | -------- | ----------------------------------------------------------- |
| `name` | string | Yes | Project name (minimum 1 character) |
| `domain` | string \| null | No | Primary domain for the project (URL format or empty string) |
| `cors` | string\[] | No | Array of allowed CORS origins (default: `[]`) |
| `crossDomain` | boolean | No | Enable cross-domain tracking (default: `false`) |
| `types` | string\[] | No | Project types: `website`, `app`, `backend` (default: `[]`) |
#### Project Types
* `website`: Web-based project
* `app`: Mobile application
* `backend`: Backend/server-side project
#### Example Request
```bash
curl -X POST 'https://api.openpanel.dev/manage/projects' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"name": "My New Project",
"domain": "https://example.com",
"cors": ["https://example.com", "https://www.example.com"],
"crossDomain": false,
"types": ["website"]
}'
```
#### Response
```json
{
"data": {
"id": "my-new-project",
"name": "My New Project",
"organizationId": "org_123",
"domain": "https://example.com",
"cors": ["https://example.com", "https://www.example.com"],
"crossDomain": false,
"allowUnsafeRevenueTracking": false,
"filters": [],
"types": ["website"],
"eventsCount": 0,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z",
"deleteAt": null,
"client": {
"id": "fa0c2780-55f2-4d9e-bea0-da2e02c7b1a9",
"secret": "sec_6c8ae85a092d6c66b242"
}
}
}
```
**Important**: The `client.secret` is only returned once when the project is created. Store it securely immediately.
### Update Project
Update an existing project's configuration.
```
PATCH /manage/projects/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------- |
| `id` | string | The ID of the project |
#### Request Body
All fields are optional. Only include fields you want to update.
| Parameter | Type | Description |
| ---------------------------- | -------------- | -------------------------------------------------- |
| `name` | string | Project name (minimum 1 character) |
| `domain` | string \| null | Primary domain (URL format, empty string, or null) |
| `cors` | string\[] | Array of allowed CORS origins |
| `crossDomain` | boolean | Enable cross-domain tracking |
| `allowUnsafeRevenueTracking` | boolean | Allow revenue tracking without client secret |
#### Example Request
```bash
curl -X PATCH 'https://api.openpanel.dev/manage/projects/my-project' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"name": "Updated Project Name",
"crossDomain": true,
"allowUnsafeRevenueTracking": false
}'
```
#### Response
```json
{
"data": {
"id": "my-project",
"name": "Updated Project Name",
"organizationId": "org_123",
"domain": "https://example.com",
"cors": ["https://example.com"],
"crossDomain": true,
"allowUnsafeRevenueTracking": false,
"filters": [],
"types": ["website"],
"eventsCount": 0,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T11:00:00.000Z",
"deleteAt": null
}
}
```
### Delete Project
Soft delete a project. The project will be scheduled for deletion after 24 hours.
```
DELETE /manage/projects/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------- |
| `id` | string | The ID of the project |
#### Example Request
```bash
curl -X DELETE 'https://api.openpanel.dev/manage/projects/my-project' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"success": true
}
```
**Note**: Projects are soft-deleted. The `deleteAt` field is set to 24 hours in the future. You can cancel deletion by updating the project before the deletion time.
## Error Handling
The API uses standard HTTP response codes. Common error responses:
### 400 Bad Request
```json
{
"error": "Bad Request",
"message": "Invalid request body",
"details": [
{
"path": ["name"],
"message": "String must contain at least 1 character(s)"
}
]
}
```
### 401 Unauthorized
```json
{
"error": "Unauthorized",
"message": "Manage: Only root clients are allowed to manage resources"
}
```
### 404 Not Found
```json
{
"error": "Not Found",
"message": "Project not found"
}
```
### 429 Too Many Requests
Rate limiting response includes headers indicating your rate limit status.
## Rate Limiting
The Projects API implements rate limiting:
* **20 requests per 10 seconds** per client
* Rate limit headers included in responses
* Implement exponential backoff for retries
## Notes
* Project IDs are automatically generated from the project name using a slug format
* If a project ID already exists, a numeric suffix is added
* CORS domains are automatically normalized (trailing slashes removed)
* The default client created with a project has `type: 'write'`
* Projects are scoped to your organization - you can only manage projects in your organization
* Soft-deleted projects are excluded from list endpoints
---
## References
URL: https://openpanel.dev/docs/api/manage/references
## Authentication
To authenticate with the References API, you need to use your `clientId` and `clientSecret` from a root client. Root clients have organization-wide access.
For detailed authentication information, see the [Authentication](/docs/api/authentication) guide.
Include the following headers with your requests:
* `openpanel-client-id`: Your OpenPanel root client ID
* `openpanel-client-secret`: Your OpenPanel root client secret
## Base URL
All References API requests should be made to:
```
https://api.openpanel.dev/manage/references
```
## What are References?
References are markers you can add to your analytics timeline to track important events such as:
* Product launches
* Marketing campaign start dates
* Feature releases
* Website redesigns
* Major announcements
References appear in your analytics charts and help you correlate changes in metrics with specific events.
## Endpoints
### List References
Retrieve all references in your organization, optionally filtered by project.
```
GET /manage/references
```
#### Query Parameters
| Parameter | Type | Description |
| ----------- | ------ | ----------------------------------------- |
| `projectId` | string | Optional. Filter references by project ID |
#### Example Request
```bash
# List all references
curl 'https://api.openpanel.dev/manage/references' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
# List references for a specific project
curl 'https://api.openpanel.dev/manage/references?projectId=my-project' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"data": [
{
"id": "1af09627-2dd7-4b37-9e5c-6ffa7e450e85",
"title": "Product Launch",
"description": "Version 2.0 released",
"date": "2024-01-15T10:00:00.000Z",
"projectId": "my-project",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-10T08:00:00.000Z"
},
{
"id": "2bf19738-3ee8-4c48-af6d-7ggb8f561f96",
"title": "Marketing Campaign Start",
"description": "Q1 2024 campaign launched",
"date": "2024-01-20T09:00:00.000Z",
"projectId": "my-project",
"createdAt": "2024-01-18T10:00:00.000Z",
"updatedAt": "2024-01-18T10:00:00.000Z"
}
]
}
```
### Get Reference
Retrieve a specific reference by ID.
```
GET /manage/references/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | ------------------------------ |
| `id` | string | The ID of the reference (UUID) |
#### Example Request
```bash
curl 'https://api.openpanel.dev/manage/references/1af09627-2dd7-4b37-9e5c-6ffa7e450e85' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"data": {
"id": "1af09627-2dd7-4b37-9e5c-6ffa7e450e85",
"title": "Product Launch",
"description": "Version 2.0 released",
"date": "2024-01-15T10:00:00.000Z",
"projectId": "my-project",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-10T08:00:00.000Z"
}
}
```
### Create Reference
Create a new reference point for a project.
```
POST /manage/references
```
#### Request Body
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------------- |
| `projectId` | string | Yes | The ID of the project this reference belongs to |
| `title` | string | Yes | Reference title (minimum 1 character) |
| `description` | string | No | Optional description or notes |
| `datetime` | string | Yes | Date and time for the reference (ISO 8601 format) |
#### Example Request
```bash
curl -X POST 'https://api.openpanel.dev/manage/references' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"projectId": "my-project",
"title": "Product Launch",
"description": "Version 2.0 released with new features",
"datetime": "2024-01-15T10:00:00.000Z"
}'
```
#### Response
```json
{
"data": {
"id": "1af09627-2dd7-4b37-9e5c-6ffa7e450e85",
"title": "Product Launch",
"description": "Version 2.0 released with new features",
"date": "2024-01-15T10:00:00.000Z",
"projectId": "my-project",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-10T08:00:00.000Z"
}
}
```
**Note**: The `date` field in the response is parsed from the `datetime` string you provided.
### Update Reference
Update an existing reference.
```
PATCH /manage/references/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | ------------------------------ |
| `id` | string | The ID of the reference (UUID) |
#### Request Body
All fields are optional. Only include fields you want to update.
| Parameter | Type | Description |
| ------------- | -------------- | ------------------------------------------------- |
| `title` | string | Reference title (minimum 1 character) |
| `description` | string \| null | Description or notes (set to `null` to clear) |
| `datetime` | string | Date and time for the reference (ISO 8601 format) |
#### Example Request
```bash
curl -X PATCH 'https://api.openpanel.dev/manage/references/1af09627-2dd7-4b37-9e5c-6ffa7e450e85' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"title": "Product Launch v2.1",
"description": "Updated: Version 2.1 released with bug fixes",
"datetime": "2024-01-15T10:00:00.000Z"
}'
```
#### Response
```json
{
"data": {
"id": "1af09627-2dd7-4b37-9e5c-6ffa7e450e85",
"title": "Product Launch v2.1",
"description": "Updated: Version 2.1 released with bug fixes",
"date": "2024-01-15T10:00:00.000Z",
"projectId": "my-project",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-10T09:30:00.000Z"
}
}
```
### Delete Reference
Permanently delete a reference. This action cannot be undone.
```
DELETE /manage/references/{id}
```
#### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | ------------------------------ |
| `id` | string | The ID of the reference (UUID) |
#### Example Request
```bash
curl -X DELETE 'https://api.openpanel.dev/manage/references/1af09627-2dd7-4b37-9e5c-6ffa7e450e85' \
-H 'openpanel-client-id: YOUR_ROOT_CLIENT_ID' \
-H 'openpanel-client-secret: YOUR_ROOT_CLIENT_SECRET'
```
#### Response
```json
{
"success": true
}
```
## Error Handling
The API uses standard HTTP response codes. Common error responses:
### 400 Bad Request
```json
{
"error": "Bad Request",
"message": "Invalid request body",
"details": [
{
"path": ["title"],
"message": "String must contain at least 1 character(s)"
}
]
}
```
### 401 Unauthorized
```json
{
"error": "Unauthorized",
"message": "Manage: Only root clients are allowed to manage resources"
}
```
### 404 Not Found
```json
{
"error": "Not Found",
"message": "Reference not found"
}
```
This error can occur if:
* The reference ID doesn't exist
* The reference belongs to a different organization
### 429 Too Many Requests
Rate limiting response includes headers indicating your rate limit status.
## Rate Limiting
The References API implements rate limiting:
* **20 requests per 10 seconds** per client
* Rate limit headers included in responses
* Implement exponential backoff for retries
## Date Format
References use ISO 8601 date format. Examples:
* `2024-01-15T10:00:00.000Z` - UTC timezone
* `2024-01-15T10:00:00-05:00` - Eastern Time (UTC-5)
* `2024-01-15` - Date only (time defaults to 00:00:00)
The `datetime` field in requests is converted to a `date` field in responses, stored as a timestamp.
## Use Cases
References are useful for:
* **Product Launches**: Mark when new versions or features are released
* **Marketing Campaigns**: Track campaign start and end dates
* **Website Changes**: Note when major redesigns or updates occur
* **Business Events**: Record important business milestones
* **A/B Testing**: Mark when experiments start or end
* **Seasonal Events**: Track holidays, sales periods, or seasonal changes
## Notes
* Reference IDs are UUIDs (Universally Unique Identifiers)
* References are scoped to projects - each reference belongs to a specific project
* References are scoped to your organization - you can only manage references for projects in your organization
* The `description` field is optional and can be set to `null` to clear it
* References appear in analytics charts to help correlate metrics with events
* When filtering by `projectId`, the project must exist and belong to your organization
---
## Track
URL: https://openpanel.dev/docs/api/track
## Good to know
* If you want to track **geo location** you'll need to pass the `ip` property as a header `x-client-ip`
* If you want to track **device information** you'll need to pass the `user-agent` property as a header `user-agent`
## Authentication
All requests to the OpenPanel API require authentication. You'll need to include your `clientId` and `clientSecret` in the headers of each request.
```bash
-H "openpanel-client-id: YOUR_CLIENT_ID" \
-H "openpanel-client-secret: YOUR_CLIENT_SECRET"
```
## Usage
### Base URL
All API requests should be made to:
```
https://api.openpanel.dev
```
### Tracking Events
To track an event:
```bash
curl -X POST https://api.openpanel.dev/track \
-H "Content-Type: application/json" \
-H "openpanel-client-id: YOUR_CLIENT_ID" \
-H "openpanel-client-secret: YOUR_CLIENT_SECRET" \
-d '{
"type": "track",
"payload": {
"name": "my_event",
"properties": {
"foo": "bar"
}
}
}'
```
### Identifying Users
To identify a user:
```bash
curl -X POST https://api.openpanel.dev/track \
-H "Content-Type: application/json" \
-H "openpanel-client-id: YOUR_CLIENT_ID" \
-H "openpanel-client-secret: YOUR_CLIENT_SECRET" \
-d '{
"type": "identify",
"payload": {
"profileId": "123",
"firstName": "Joe",
"lastName": "Doe",
"email": "joe@doe.com",
"properties": {
"tier": "premium"
}
}
}'
```
### Incrementing Properties
To increment a numeric property:
```bash
curl -X POST https://api.openpanel.dev/track \
-H "Content-Type: application/json" \
-H "openpanel-client-id: YOUR_CLIENT_ID" \
-H "openpanel-client-secret: YOUR_CLIENT_SECRET" \
-d '{
"type": "increment",
"payload": {
"profileId": "1",
"property": "visits",
"value": 1
}
}'
```
### Decrementing Properties
To decrement a numeric property:
```bash
curl -X POST https://api.openpanel.dev/track \
-H "Content-Type: application/json" \
-H "openpanel-client-id: YOUR_CLIENT_ID" \
-H "openpanel-client-secret: YOUR_CLIENT_SECRET" \
-d '{
"type": "decrement",
"payload": {
"profileId": "1",
"property": "visits",
"value": 1
}
}'
```
### Error Handling
The API uses standard HTTP response codes to indicate the success or failure of requests. In case of an error, the response body will contain more information about the error.
Example error response:
```json
{
"error": "Invalid client credentials",
"status": 401
}
```
### Rate Limiting
The API implements rate limiting to prevent abuse. If you exceed the rate limit, you'll receive a 429 (Too Many Requests) response. The response will include headers indicating your rate limit status.
Best Practices
1. Always use HTTPS to ensure secure communication.
2. Store your clientId and clientSecret securely and never expose them in client-side code.
3. Implement proper error handling in your applications.
4. Respect rate limits and implement exponential backoff for retries.
---
## Identify Users
URL: https://openpanel.dev/docs/get-started/identify-users
By default, OpenPanel tracks visitors anonymously. To connect these events to a specific user in your database, you need to identify them.
## How it works
When a user logs in or signs up, you should call the `identify` method. This associates their current session and all future events with their unique ID from your system.
```javascript
op.identify({
profileId: 'user_123'
});
```
## Adding user traits
You can also pass user traits (like name, email, or plan type) when you identify them. These traits will appear in the user's profile in your dashboard.
```javascript
op.identify({
profileId: 'user_123',
firstName: 'Jane',
lastName: 'Doe',
email: 'jane@example.com',
company: 'Acme Inc'
});
```
### Standard traits
We recommend using these standard keys for common user information so they display correctly in the OpenPanel dashboard:
* `firstName`
* `lastName`
* `email`
* `phone`
* `avatar`
## Best Practices
1. **Call on login**: Always identify the user immediately after they log in.
2. **Call on update**: If a user updates their profile, call identify again with the new information.
3. **Unique IDs**: Use a stable, unique ID from your database (like a UUID) rather than an email address or username that might change.
---
## Install OpenPanel
URL: https://openpanel.dev/docs/get-started/install-openpanel
import { Cards, Card } from 'fumadocs-ui/components/card';
import { Code, Globe, Layout, Smartphone, FileJson } from 'lucide-react';
The quickest way to get started with OpenPanel is to use our Web SDK. It works with any website.
## Quick Start
Simply add this script tag to your website's `
` 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.fetchDeviceId()`, 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: await op.fetchDeviceId(), // ✅ 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.fetchDeviceId(): Promise
```
---
## 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
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! 💙