# 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! 💙