diff --git a/apps/public/content/docs/(tracking)/sdks/astro.mdx b/apps/public/content/docs/(tracking)/sdks/astro.mdx
index 19534ed9..3826000d 100644
--- a/apps/public/content/docs/(tracking)/sdks/astro.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/astro.mdx
@@ -3,9 +3,14 @@ title: Astro
---
import { Step, Steps } from 'fumadocs-ui/components/steps';
+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 [Astro analytics guide](/guides/astro-analytics).
+
+
## Installation
diff --git a/apps/public/content/docs/(tracking)/sdks/express.mdx b/apps/public/content/docs/(tracking)/sdks/express.mdx
index 5d606acd..4160dee2 100644
--- a/apps/public/content/docs/(tracking)/sdks/express.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/express.mdx
@@ -4,11 +4,16 @@ description: The Express middleware is a basic wrapper around Javascript SDK. It
---
import Link from 'next/link';
+import { Callout } from 'fumadocs-ui/components/callout';
import { DeviceIdWarning } from '@/components/device-id-warning';
import { PersonalDataWarning } from '@/components/personal-data-warning';
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
+
+Looking for a step-by-step tutorial? Check out the [Express analytics guide](/guides/express-analytics).
+
+
## Installation
```bash
diff --git a/apps/public/content/docs/(tracking)/sdks/index.mdx b/apps/public/content/docs/(tracking)/sdks/index.mdx
index 5bffc11b..37fad2c4 100644
--- a/apps/public/content/docs/(tracking)/sdks/index.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/index.mdx
@@ -13,7 +13,7 @@ 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
+- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js applications | [Setup guide](/guides/nextjs-analytics)
## Web & Browser SDKs
@@ -38,12 +38,12 @@ For most web projects, we recommend starting with one of these:
### Frontend Frameworks
-- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js
+- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js | [Setup guide](/guides/nextjs-analytics)
- ✅ Server and client side tracking
- ✅ App Router support
- ✅ Automatic route tracking
-- **[Astro](/docs/sdks/astro)** - Astro framework integration
+- **[Astro](/docs/sdks/astro)** - Astro framework integration | [Setup guide](/guides/astro-analytics)
- ✅ Static and SSR support
- ✅ Component-based tracking
- ✅ Island architecture compatible
@@ -51,10 +51,10 @@ For most web projects, we recommend starting with one of these:
- **[React](/docs/sdks/react)** - React integration
- 📝 Use Script Tag or Web SDK for now
-- **[Vue](/docs/sdks/vue)** - Vue.js integration
+- **[Vue](/docs/sdks/vue)** - Vue.js integration | [Setup guide](/guides/vue-analytics)
- 📝 Use Script Tag or Web SDK for now
-- **[Remix](/docs/sdks/remix)** - Remix framework integration
+- **[Remix](/docs/sdks/remix)** - Remix framework integration | [Setup guide](/guides/remix-analytics)
- 📝 Use Script Tag or Web SDK for now
- **Svelte** - Svelte integration
@@ -71,12 +71,12 @@ For most web projects, we recommend starting with one of these:
## Server-Side SDKs
-- **[Node.js (Express)](/docs/sdks/express)** - Express.js middleware
+- **[Node.js (Express)](/docs/sdks/express)** - Express.js middleware | [Setup guide](/guides/express-analytics)
- ✅ Request tracking
- ✅ Error tracking
- ✅ Custom middleware support
-- **[Python](/docs/sdks/python)** - Python SDK for server-side tracking
+- **[Python](/docs/sdks/python)** - Python SDK for server-side tracking | [Setup guide](/guides/python-analytics)
- ✅ Thread-safe
- ✅ Async support
- ✅ Django and Flask compatible
@@ -93,18 +93,18 @@ For most web projects, we recommend starting with one of these:
## Mobile SDKs
-- **[React Native](/docs/sdks/react-native)** - Cross-platform mobile analytics
+- **[React Native](/docs/sdks/react-native)** - Cross-platform mobile analytics | [Setup guide](/guides/react-native-analytics)
- ✅ iOS and Android support
- ✅ Native performance
- ✅ Offline support
-- **[Swift](/docs/sdks/swift)** - Native iOS, macOS, tvOS, and watchOS SDK
+- **[Swift](/docs/sdks/swift)** - Native iOS, macOS, tvOS, and watchOS SDK | [Setup guide](/guides/swift-analytics)
- ✅ Apple platform support
- ✅ Swift Package Manager
- ✅ Thread-safe API
- ✅ Automatic lifecycle tracking
-- **[Kotlin / Android](/docs/sdks/kotlin)** - Native Android SDK
+- **[Kotlin / Android](/docs/sdks/kotlin)** - Native Android SDK | [Setup guide](/guides/kotlin-analytics)
- ✅ Android support
- ✅ System information collection
- ✅ Thread-safe API
diff --git a/apps/public/content/docs/(tracking)/sdks/kotlin.mdx b/apps/public/content/docs/(tracking)/sdks/kotlin.mdx
index 8ebf9723..f3613cee 100644
--- a/apps/public/content/docs/(tracking)/sdks/kotlin.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/kotlin.mdx
@@ -9,6 +9,10 @@ import CommonSdkConfig from '@/components/common-sdk-config.mdx';
The OpenPanel Kotlin SDK allows you to track user behavior in your Kotlin applications. This guide provides instructions for installing and using the Kotlin SDK in your project.
+
+Looking for a step-by-step tutorial? Check out the [Kotlin analytics guide](/guides/kotlin-analytics).
+
+
## Installation
diff --git a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx
index 9fec63d3..6f317326 100644
--- a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx
@@ -7,9 +7,14 @@ 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 [Next.js analytics guide](/guides/nextjs-analytics).
+
+
## Good to know
Keep in mind that all tracking here happens on the client!
diff --git a/apps/public/content/docs/(tracking)/sdks/python.mdx b/apps/public/content/docs/(tracking)/sdks/python.mdx
index 07e2b57e..af93a947 100644
--- a/apps/public/content/docs/(tracking)/sdks/python.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/python.mdx
@@ -8,6 +8,10 @@ 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
diff --git a/apps/public/content/docs/(tracking)/sdks/react-native.mdx b/apps/public/content/docs/(tracking)/sdks/react-native.mdx
index fd79c5aa..aadf014f 100644
--- a/apps/public/content/docs/(tracking)/sdks/react-native.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/react-native.mdx
@@ -8,8 +8,13 @@ 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
diff --git a/apps/public/content/docs/(tracking)/sdks/swift.mdx b/apps/public/content/docs/(tracking)/sdks/swift.mdx
index ecaa0bb4..c6bc10ac 100644
--- a/apps/public/content/docs/(tracking)/sdks/swift.mdx
+++ b/apps/public/content/docs/(tracking)/sdks/swift.mdx
@@ -9,6 +9,10 @@ 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
diff --git a/apps/public/content/guides/astro-analytics.mdx b/apps/public/content/guides/astro-analytics.mdx
new file mode 100644
index 00000000..6c1dfe82
--- /dev/null
+++ b/apps/public/content/guides/astro-analytics.mdx
@@ -0,0 +1,171 @@
+---
+title: "How to add analytics to Astro"
+description: "Add privacy-first analytics to your Astro site with OpenPanel. Track page views, custom events, and user behavior without cookies."
+difficulty: beginner
+timeToComplete: 5
+date: 2025-12-14
+cover: /content/cover-default.jpg
+team: OpenPanel Team
+steps:
+ - name: "Install the SDK"
+ anchor: "install"
+ - name: "Add the component to your layout"
+ anchor: "setup"
+ - name: "Track custom events"
+ anchor: "events"
+ - name: "Identify users"
+ anchor: "identify"
+ - name: "Verify your setup"
+ anchor: "verify"
+---
+
+# How to add analytics to Astro
+
+Adding analytics to your Astro site helps you understand how visitors interact with your content. This guide walks you through setting up OpenPanel to track page views, custom events, and user behavior in about five minutes.
+
+OpenPanel works well with Astro because it's a lightweight script that loads asynchronously and doesn't block your site's rendering. It tracks page views automatically across both static and server-rendered pages, and the component-based API fits naturally into Astro's architecture.
+
+## Prerequisites
+
+- An Astro project
+- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding))
+- Your Client ID from the OpenPanel dashboard
+
+## Install the SDK [#install]
+
+Start by adding the OpenPanel Astro package to your project. This package provides Astro components that handle initialization and tracking.
+
+```bash
+npm install @openpanel/astro
+```
+
+You can also use pnpm or yarn if that's your preference.
+
+## Add the component to your layout [#setup]
+
+The `OpenPanelComponent` initializes tracking and should be placed in your root layout so it loads on every page. Add it inside the `` tag to ensure it initializes before any user interactions.
+
+```astro
+---
+import { OpenPanelComponent } from '@openpanel/astro';
+---
+
+
+
+
+
+
+
+
+
+```
+
+The `trackScreenViews` option automatically records a page view event whenever someone navigates to a new page. The `trackOutgoingLinks` option tracks when visitors click links that take them to external sites. Both are optional but recommended for most sites.
+
+You can also pass a `profileId` prop if you already know the user's identity at render time, and `globalProperties` to attach metadata to every event.
+
+## Track custom events [#events]
+
+Beyond automatic page views, you'll want to track specific interactions that matter to your business. OpenPanel exposes a global `op` function that you can call from any event handler.
+
+```astro
+
+```
+
+The first argument is always `'track'`, the second is your event name, and the third is an optional object of properties you want to attach to the event. Keep event names consistent across your codebase, using snake_case is a good convention.
+
+For elements where you'd rather not write JavaScript, you can use data attributes instead. Any element with a `data-track` attribute will automatically fire an event when clicked.
+
+```astro
+
+```
+
+Properties are pulled from any `data-*` attributes on the element. The `data-track` value becomes the event name, and other data attributes become event properties.
+
+### Tracking form submissions
+
+Forms are a common tracking target. You can fire an event in the `onsubmit` handler while still allowing the form to submit normally.
+
+```astro
+
+```
+
+The `return true` ensures the form submission continues after the tracking call.
+
+## Identify users [#identify]
+
+When a user logs in or you otherwise learn their identity, you can associate their activity with a profile. The `IdentifyComponent` handles this declaratively.
+
+```astro
+---
+import { IdentifyComponent } from '@openpanel/astro';
+
+const user = await getCurrentUser();
+---
+
+
+```
+
+Place this component on pages where the user is authenticated. Once identified, all subsequent events from that browser session will be linked to this profile until they clear their browser data or you explicitly clear the identity.
+
+### Setting global properties
+
+Sometimes you want to attach the same properties to every event, like an app version or environment. The `SetGlobalPropertiesComponent` lets you do this once rather than repeating it in every tracking call.
+
+```astro
+---
+import { SetGlobalPropertiesComponent } from '@openpanel/astro';
+---
+
+
+```
+
+These properties merge with any event-specific properties you pass to individual tracking calls.
+
+## Verify your setup [#verify]
+
+Open your Astro site in the browser and navigate between a few pages. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view. You should see page view events appearing within seconds.
+
+If events aren't showing up, open your browser's developer console and look for errors. The most common issues are an incorrect Client ID or an ad blocker preventing the tracking script from loading. You can also check the Network tab to confirm requests are being sent to OpenPanel's servers.
+
+## Next steps
+
+The [Astro SDK reference](/docs/sdks/astro) covers additional configuration options like filtering events and customizing the CDN URL. If you're interested in running OpenPanel on your own infrastructure, the [self-hosting guide](/articles/how-to-self-host-openpanel) walks through the setup process.
+
+
+
+Yes. The OpenPanelComponent is a client-side script that hydrates independently of your island components. It will track interactions across your entire page regardless of which parts are hydrated.
+
+
+
+Yes. OpenPanel works with both static and server-rendered Astro sites. The tracking script runs in the browser regardless of how the page was rendered.
+
+
+
+No. OpenPanel uses cookieless tracking by default, which means you don't need cookie consent banners for basic analytics under most privacy regulations including GDPR. Read more about [cookieless analytics](/articles/cookieless-analytics).
+
+
diff --git a/apps/public/content/guides/ecommerce-tracking.mdx b/apps/public/content/guides/ecommerce-tracking.mdx
new file mode 100644
index 00000000..a3b86743
--- /dev/null
+++ b/apps/public/content/guides/ecommerce-tracking.mdx
@@ -0,0 +1,200 @@
+---
+title: "How to track e-commerce events and revenue"
+description: "Track product views, cart activity, and purchases with OpenPanel to understand your e-commerce funnel and revenue."
+difficulty: intermediate
+timeToComplete: 10
+date: 2025-12-15
+cover: /content/cover-default.jpg
+team: OpenPanel Team
+steps:
+ - name: "Track product views"
+ anchor: "track-product-views"
+ - name: "Track cart activity"
+ anchor: "track-cart-activity"
+ - name: "Track purchases and revenue"
+ anchor: "track-purchases-and-revenue"
+ - name: "Verify your setup"
+ anchor: "verify"
+---
+
+# How to track e-commerce events and revenue
+
+E-commerce tracking gives you visibility into how users interact with your products and what drives purchases. By the end of this guide, you'll have product views, cart events, and revenue tracking working in your store.
+
+OpenPanel tracks revenue using a dedicated `revenue()` method that links payments to visitor sessions. This lets you see which traffic sources, campaigns, and pages generate the most revenue. You can track from your frontend for quick setup, or from your backend via webhooks for more accurate data.
+
+## Prerequisites
+
+- An OpenPanel account
+- Your Client ID from the [dashboard](https://dashboard.openpanel.dev)
+- The OpenPanel SDK installed in your project
+
+## Track product views [#track-product-views]
+
+Product view tracking helps you understand which items attract attention. When a user lands on a product page, fire an event with the product details so you can later analyze which products get viewed but not purchased.
+
+```tsx
+function ProductPage({ product }) {
+ const op = useOpenPanel();
+
+ useEffect(() => {
+ op.track('product_viewed', {
+ product_id: product.id,
+ product_name: product.name,
+ product_category: product.category,
+ product_price: product.price,
+ currency: 'USD',
+ });
+ }, [product.id]);
+
+ return
{product.name}
;
+}
+```
+
+Include consistent properties across all your product events. Using the same `product_id` format everywhere makes it easy to build reports that connect views to purchases.
+
+## Track cart activity [#track-cart-activity]
+
+Cart events reveal where users drop off in the purchase process. Track both additions and removals to understand cart abandonment patterns.
+
+When a user adds an item, capture the product details along with the current cart value. This gives you context about order sizes at different stages of the funnel.
+
+```tsx
+function ProductCard({ product, cart }) {
+ const op = useOpenPanel();
+
+ const handleAddToCart = () => {
+ op.track('product_added_to_cart', {
+ product_id: product.id,
+ product_name: product.name,
+ product_price: product.price,
+ quantity: 1,
+ cart_value: cart.total + product.price,
+ currency: 'USD',
+ });
+
+ addToCart(product);
+ };
+
+ return (
+
+ );
+}
+```
+
+Track removals the same way. The symmetry between add and remove events makes it straightforward to calculate net cart changes.
+
+```tsx
+const handleRemoveFromCart = (item) => {
+ op.track('product_removed_from_cart', {
+ product_id: item.id,
+ product_name: item.name,
+ product_price: item.price,
+ quantity: item.quantity,
+ currency: 'USD',
+ });
+
+ removeFromCart(item);
+};
+```
+
+## Track purchases and revenue [#track-purchases-and-revenue]
+
+Revenue tracking is where e-commerce analytics becomes actionable. OpenPanel provides dedicated methods for revenue that link payments back to visitor sessions, so you can see which traffic sources generate the most value.
+
+For frontend tracking, use `pendingRevenue()` before redirecting to checkout, then `flushRevenue()` on your success page. This approach works well when you want to get started quickly without backend changes.
+
+```tsx
+async function handleCheckout(cart) {
+ const op = useOpenPanel();
+
+ op.pendingRevenue(cart.total, {
+ order_items: cart.items.length,
+ currency: 'USD',
+ });
+
+ window.location.href = await createCheckoutUrl(cart);
+}
+```
+
+On your success page, flush the pending revenue to send it to OpenPanel.
+
+```tsx
+function SuccessPage() {
+ const op = useOpenPanel();
+
+ useEffect(() => {
+ op.flushRevenue();
+ }, []);
+
+ return
Thank you for your purchase!
;
+}
+```
+
+For more accurate tracking, handle revenue in your backend webhook. This ensures you only record completed payments. Pass the visitor's `deviceId` when creating the checkout so you can link the payment back to their session.
+
+```tsx
+// Frontend: include deviceId when starting checkout
+const deviceId = await op.fetchDeviceId();
+
+const response = await fetch('/api/checkout', {
+ method: 'POST',
+ body: JSON.stringify({
+ deviceId,
+ items: cart.items,
+ }),
+});
+```
+
+In your webhook handler, use that `deviceId` to attribute the revenue.
+
+```javascript
+// Backend: webhook handler
+export async function POST(req) {
+ const event = await req.json();
+
+ if (event.type === 'checkout.session.completed') {
+ const session = event.data.object;
+
+ op.revenue(session.amount_total, {
+ deviceId: session.metadata.deviceId,
+ });
+ }
+
+ return Response.json({ received: true });
+}
+```
+
+If your users are logged in, you can use `profileId` instead of `deviceId`. This simplifies the flow since you don't need to capture the device ID during checkout.
+
+## Verify your setup [#verify]
+
+Open your OpenPanel dashboard and trigger a few events manually. Add a product to your cart, then check the live events view to confirm the events are arriving with the correct properties.
+
+For revenue tracking, you can test with a small transaction or use your payment provider's test mode. Look for your revenue events in the dashboard and verify the amounts match what you expect.
+
+If events aren't appearing, check that your Client ID is correct and that ad blockers aren't interfering. The browser's network tab can help you confirm requests are being sent.
+
+## Next steps
+
+Once you have basic e-commerce tracking working, you can build purchase funnels to visualize conversion rates at each step. The [revenue tracking documentation](/docs/revenue-tracking) covers advanced patterns like subscription tracking and refunds. For a deeper understanding of attribution, read about how OpenPanel's [cookieless tracking](/articles/cookieless-analytics) works.
+
+To learn more about tracking custom events in general, check out the [track custom events guide](/guides/track-custom-events) which covers event structure, properties, and common patterns.
+
+
+
+Yes. Once you track revenue using the `revenue()` or `flushRevenue()` methods, OpenPanel calculates totals, averages, and breakdowns by source automatically. You can view these in the revenue section of your dashboard.
+
+
+
+Backend tracking via webhooks is more accurate since it only records completed payments. Frontend tracking is faster to implement but may count abandoned checkouts. For production stores, backend tracking is recommended.
+
+
+
+Yes. Track subscription events like any other revenue event, including properties for plan name and billing period. The revenue tracking documentation covers subscription-specific patterns in detail.
+
+
+
+Track refunds as separate events with the refund amount. This lets you calculate net revenue by subtracting refunds from gross revenue in your reports.
+
+
diff --git a/apps/public/content/guides/express-analytics.mdx b/apps/public/content/guides/express-analytics.mdx
new file mode 100644
index 00000000..93ed10c1
--- /dev/null
+++ b/apps/public/content/guides/express-analytics.mdx
@@ -0,0 +1,219 @@
+---
+title: "How to add analytics to Express"
+description: "Add server-side analytics to your Express application with OpenPanel middleware. Track API requests, user actions, and custom events."
+difficulty: beginner
+timeToComplete: 8
+date: 2025-12-15
+cover: /content/cover-default.jpg
+team: OpenPanel Team
+steps:
+ - name: "Install the SDK"
+ anchor: "install"
+ - name: "Add the middleware"
+ anchor: "middleware"
+ - name: "Track events"
+ anchor: "events"
+ - name: "Identify users"
+ anchor: "identify"
+ - name: "Verify your setup"
+ anchor: "verify"
+---
+
+# How to add analytics to Express
+
+Server-side analytics gives you reliable event tracking that cannot be blocked by ad blockers or browser extensions. The OpenPanel Express middleware wraps the JavaScript SDK and attaches it to every request, making it simple to track events throughout your application.
+
+OpenPanel is an open-source alternative to Mixpanel and Amplitude. You get powerful analytics with full control over your data, and you can [self-host](/articles/how-to-self-host-openpanel) if privacy requirements demand it.
+
+## Prerequisites
+
+- An Express application
+- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding))
+- Your Client ID and Client Secret from the OpenPanel dashboard
+
+Server-side tracking requires a `clientSecret` for authentication since the server cannot rely on browser CORS headers to verify the request origin.
+
+## Install the SDK [#install]
+
+The Express SDK is a lightweight middleware that creates an OpenPanel instance for each request. Install it with npm (pnpm and yarn work too).
+
+```bash
+npm install @openpanel/express
+```
+
+## Add the middleware [#middleware]
+
+The middleware attaches the OpenPanel SDK to every request as `req.op`. Add it early in your middleware chain so it is available in all your route handlers.
+
+```ts
+import express from 'express';
+import createOpenpanelMiddleware from '@openpanel/express';
+
+const app = express();
+
+app.use(express.json());
+
+app.use(
+ createOpenpanelMiddleware({
+ clientId: 'YOUR_CLIENT_ID',
+ clientSecret: 'YOUR_CLIENT_SECRET',
+ })
+);
+
+app.listen(3000, () => {
+ console.log('Server running on http://localhost:3000');
+});
+```
+
+You should store your credentials in environment variables rather than hardcoding them. This keeps secrets out of version control and makes it easy to use different credentials in development and production.
+
+```ts
+app.use(
+ createOpenpanelMiddleware({
+ clientId: process.env.OPENPANEL_CLIENT_ID!,
+ clientSecret: process.env.OPENPANEL_CLIENT_SECRET!,
+ })
+);
+```
+
+The middleware also forwards the client IP address and user-agent from incoming requests, so geographic and device data will be accurate even though events originate from your server.
+
+## Track events [#events]
+
+Once the middleware is in place, you can track events in any route handler by calling `req.op.track()`. The first argument is the event name and the second is an object of properties you want to attach.
+
+```ts
+app.post('/signup', async (req, res) => {
+ const { email, name } = req.body;
+
+ req.op.track('user_signed_up', {
+ email,
+ name,
+ source: 'website',
+ });
+
+ const user = await createUser({ email, name });
+ res.json({ success: true, user });
+});
+```
+
+You can track any event that matters to your business. Common examples include form submissions, purchases, feature usage, and API errors.
+
+```ts
+app.post('/contact', async (req, res) => {
+ const { email, message } = req.body;
+
+ req.op.track('contact_form_submitted', {
+ email,
+ message_length: message.length,
+ });
+
+ await sendContactEmail(email, message);
+ res.json({ success: true });
+});
+```
+
+### Automatic request tracking
+
+The middleware can automatically track every request if you provide a `trackRequest` function. This is useful for monitoring API usage without manually adding tracking calls to each route.
+
+```ts
+app.use(
+ createOpenpanelMiddleware({
+ clientId: process.env.OPENPANEL_CLIENT_ID!,
+ clientSecret: process.env.OPENPANEL_CLIENT_SECRET!,
+ trackRequest: (url) => url.startsWith('/api/'),
+ })
+);
+```
+
+When `trackRequest` returns true, the middleware sends a `request` event with the URL, method, and query parameters.
+
+## Identify users [#identify]
+
+To associate events with specific users, use the `getProfileId` option in the middleware configuration. This function receives the request object and should return the user's ID.
+
+```ts
+app.use(
+ createOpenpanelMiddleware({
+ clientId: process.env.OPENPANEL_CLIENT_ID!,
+ clientSecret: process.env.OPENPANEL_CLIENT_SECRET!,
+ getProfileId: (req) => req.user?.id,
+ })
+);
+```
+
+You can also send user profile data with `req.op.identify()`. This updates the user's profile in OpenPanel with properties like name, email, and any custom attributes.
+
+```ts
+app.post('/login', async (req, res) => {
+ const { email, password } = req.body;
+ const user = await authenticateUser(email, password);
+
+ req.op.identify({
+ profileId: user.id,
+ firstName: user.firstName,
+ lastName: user.lastName,
+ email: user.email,
+ properties: {
+ plan: user.plan,
+ signupDate: user.createdAt,
+ },
+ });
+
+ req.op.track('user_logged_in', { method: 'email' });
+ res.json({ success: true, user });
+});
+```
+
+### Increment profile properties
+
+If you want to track cumulative values on a user profile, like login count or total purchases, use the `increment` method.
+
+```ts
+req.op.increment({
+ profileId: user.id,
+ property: 'login_count',
+ value: 1,
+});
+```
+
+## Verify your setup [#verify]
+
+Start your Express server and trigger a few events by making requests to your endpoints. Open the [OpenPanel dashboard](https://dashboard.openpanel.dev) and navigate to the Real-time view to see events as they arrive.
+
+If events are not appearing, check your server logs for error responses from OpenPanel. Verify that both `clientId` and `clientSecret` are correct and that the middleware is added before your routes.
+
+## TypeScript support
+
+The Express SDK automatically extends the `Request` interface to include `req.op`. If your TypeScript configuration does not pick this up, you can extend the interface manually in a declaration file.
+
+```ts
+import { OpenPanel } from '@openpanel/express';
+
+declare global {
+ namespace Express {
+ export interface Request {
+ op: OpenPanel;
+ }
+ }
+}
+```
+
+## Next steps
+
+The [Express SDK reference](/docs/sdks/express) covers all available options and methods. If you are using a different Node.js framework, the [Node.js tracking guide](/guides/nodejs-analytics) shows how to use the base SDK directly. For comparing OpenPanel to other analytics tools, see the [Mixpanel alternative](/compare/mixpanel-alternative) page.
+
+
+
+Server-side tracking requires authentication because requests come from your server, not a browser with CORS restrictions. The clientSecret ensures events are properly authenticated and prevents unauthorized tracking from other sources.
+
+
+
+Yes. The OpenPanel SDK sends events asynchronously by default. Events are queued and dispatched in the background, so tracking calls will not block your route handlers or slow down response times.
+
+
+
+Yes. OpenPanel is designed for GDPR compliance with cookieless tracking and data minimization. Server-side tracking gives you full control over what data you collect. With self-hosting, you eliminate international data transfer concerns entirely.
+
+
diff --git a/apps/public/content/guides/kotlin-analytics.mdx b/apps/public/content/guides/kotlin-analytics.mdx
new file mode 100644
index 00000000..b0d319d9
--- /dev/null
+++ b/apps/public/content/guides/kotlin-analytics.mdx
@@ -0,0 +1,266 @@
+---
+title: "How to add analytics to Android apps"
+description: "Add privacy-first analytics to Android applications using OpenPanel's Kotlin SDK. Track events, identify users, and analyze behavior."
+type: guide
+difficulty: intermediate
+timeToComplete: 10
+date: 2025-12-15
+lastUpdated: 2025-12-15
+steps:
+ - name: "Add the dependency"
+ anchor: "install"
+ - name: "Initialize OpenPanel"
+ anchor: "setup"
+ - name: "Track events"
+ anchor: "events"
+ - name: "Identify users"
+ anchor: "identify"
+ - name: "Track screen views"
+ anchor: "screenviews"
+ - name: "Verify your setup"
+ anchor: "verify"
+---
+
+# How to add analytics to Android apps
+
+This guide walks you through adding OpenPanel analytics to an Android application using the Kotlin SDK. You'll learn how to track events, identify users, and monitor screen views across your app.
+
+OpenPanel works well for Android apps because it provides a lightweight, privacy-focused SDK that handles offline queuing and automatic system information collection. Unlike web SDKs, native apps require a client secret for authentication since CORS headers aren't available.
+
+## Prerequisites
+
+- An Android project (minSdkVersion 21+)
+- An OpenPanel account
+- Your Client ID and Client Secret from the [dashboard](https://dashboard.openpanel.dev)
+
+## Add the dependency [#install]
+
+Start by adding the OpenPanel SDK to your app's `build.gradle.kts` file. The SDK is available through standard Gradle dependency management.
+
+```kotlin
+dependencies {
+ implementation("dev.openpanel:openpanel:0.0.1")
+}
+```
+
+The Kotlin SDK is currently in development, so check the [GitHub repository](https://github.com/Openpanel-dev/kotlin-sdk) for the latest version number before adding it to your project.
+
+## Initialize OpenPanel [#setup]
+
+Before you can track events, you need to initialize OpenPanel in your Application class. This ensures the SDK is available throughout your app and can properly manage its lifecycle.
+
+```kotlin
+import android.app.Application
+import dev.openpanel.OpenPanel
+
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+
+ OpenPanel.create(
+ context = this,
+ options = OpenPanel.Options(
+ clientId = "YOUR_CLIENT_ID",
+ clientSecret = "YOUR_CLIENT_SECRET"
+ )
+ )
+ }
+}
+```
+
+You also need to register your Application class in the Android manifest so the system knows to use it.
+
+```xml
+
+
+```
+
+If you're using dependency injection with Hilt or Dagger, you can provide OpenPanel as a singleton instead. This approach integrates better with modern Android architecture patterns.
+
+```kotlin
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import dev.openpanel.OpenPanel
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppModule {
+ @Provides
+ @Singleton
+ fun provideOpenPanel(@ApplicationContext context: Context): OpenPanel {
+ return OpenPanel.create(
+ context,
+ OpenPanel.Options(
+ clientId = "YOUR_CLIENT_ID",
+ clientSecret = "YOUR_CLIENT_SECRET"
+ )
+ )
+ }
+}
+```
+
+## Track events [#events]
+
+Once OpenPanel is initialized, you can track events anywhere in your app by getting the SDK instance and calling the `track` method. Each event has a name and an optional map of properties.
+
+```kotlin
+import dev.openpanel.OpenPanel
+
+class MainActivity : AppCompatActivity() {
+ private lateinit var op: OpenPanel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ op = OpenPanel.getInstance(this)
+
+ findViewById