Skip to main content
Technology & EngineeringAnalytics Services223 lines

June.so

June.so: B2B product analytics, company-level insights, feature adoption reports, activation tracking, Node and JavaScript SDK

Quick Summary22 lines
You are an expert in integrating June.so for B2B product analytics with company-level insights.

## Key Points

- Always call `group()` after `identify()` to link users to companies. June's B2B reports (company-level retention, feature adoption per company) depend on this association.
- Use June's recommended event names (`Signed Up`, `Invite Sent`, `Feature Activated`) to take advantage of auto-generated reports instead of building custom dashboards from scratch.
- Batch server-side events and call `june.flush()` before process exit (e.g., in serverless function teardown) to avoid losing events.
- Omitting the `group()` call means company-level analytics will be empty. This is the most common integration mistake, especially when migrating from a user-only analytics tool.

## Quick Example

```bash
npm install @june-so/analytics-node
```

```html
<script>
  !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("June snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","screen","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware","registerPlugin"];analytics.factory=function(e){return function(){if(window.analytics.initialized)return window.analytics[e].apply(window.analytics,arguments);var i=Array.prototype.slice.call(arguments);i.unshift(e);analytics.push(i);return analytics}};for(var i=0;i<analytics.methods.length;i++){var key=analytics.methods[i];analytics[key]=analytics.factory(key)}analytics.load=function(key,i){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://unpkg.com/@june-so/analytics-next/dist/umd/standalone.js";t.addEventListener("load",function(e){window.analytics.load(key);window.analytics.page()});var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._writeKey=key};analytics.load("YOUR_WRITE_KEY")}}();
</script>
```
skilldb get analytics-services-skills/June.soFull skill: 223 lines
Paste into your CLAUDE.md or agent config

June.so — Analytics Integration

You are an expert in integrating June.so for B2B product analytics with company-level insights.

Core Philosophy

Overview

June.so is a product analytics platform designed for B2B SaaS companies. It provides out-of-the-box reports for activation, retention, feature adoption, and active users at both the user and company level. June auto-generates reports from standard event data and requires minimal configuration compared to general-purpose analytics tools.

Setup & Configuration

Install the SDK

npm install @june-so/analytics-node

Node.js / Server-Side Setup

import Analytics from "@june-so/analytics-node";

const june = new Analytics("YOUR_WRITE_KEY");

// Flush events on shutdown
process.on("SIGTERM", () => {
  june.flush();
});

Browser SDK (Client-Side)

<script>
  !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("June snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","screen","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware","registerPlugin"];analytics.factory=function(e){return function(){if(window.analytics.initialized)return window.analytics[e].apply(window.analytics,arguments);var i=Array.prototype.slice.call(arguments);i.unshift(e);analytics.push(i);return analytics}};for(var i=0;i<analytics.methods.length;i++){var key=analytics.methods[i];analytics[key]=analytics.factory(key)}analytics.load=function(key,i){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://unpkg.com/@june-so/analytics-next/dist/umd/standalone.js";t.addEventListener("load",function(e){window.analytics.load(key);window.analytics.page()});var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._writeKey=key};analytics.load("YOUR_WRITE_KEY")}}();
</script>

Next.js App Router Integration

// lib/june.ts
import Analytics from "@june-so/analytics-node";

let juneClient: Analytics | null = null;

export function getJune(): Analytics {
  if (!juneClient) {
    juneClient = new Analytics(process.env.JUNE_WRITE_KEY!);
  }
  return juneClient;
}

// app/api/track/route.ts
import { getJune } from "@/lib/june";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const { userId, event, properties } = await req.json();
  const june = getJune();

  june.track({
    userId,
    event,
    properties,
  });

  return NextResponse.json({ ok: true });
}

Core Patterns

Identifying Users

// Identify a user with traits
june.identify({
  userId: "user-123",
  traits: {
    name: "Alice Chen",
    email: "alice@acme.com",
    createdAt: "2025-01-15T10:00:00Z",
    plan: "growth",
    role: "admin",
  },
});

Company / Group Tracking

June's key differentiator is company-level analytics. Use group to associate users with companies:

// Associate user with a company
june.group({
  userId: "user-123",
  groupId: "company-acme",
  traits: {
    name: "Acme Corp",
    industry: "SaaS",
    plan: "enterprise",
    employeeCount: 150,
    createdAt: "2024-06-01T00:00:00Z",
    mrr: 2400,
  },
});

Tracking Events

// Track feature usage
june.track({
  userId: "user-123",
  event: "Report Generated",
  properties: {
    reportType: "revenue-forecast",
    format: "pdf",
    dataRange: "last-30-days",
  },
});

// Track activation milestones
june.track({
  userId: "user-123",
  event: "Activation Milestone",
  properties: {
    milestone: "first-integration-connected",
    daysFromSignup: 2,
  },
});

// Track page views
june.page({
  userId: "user-123",
  name: "Dashboard",
  properties: {
    section: "analytics",
  },
});

Recommended Event Naming for Auto-Reports

June auto-generates reports when you use its expected event conventions:

// Signup event — drives activation reports
june.track({ userId: "user-123", event: "Signed Up" });

// Invite events — drives virality reports
june.track({
  userId: "user-123",
  event: "Invite Sent",
  properties: { inviteeEmail: "bob@acme.com" },
});

// Feature events — drives feature adoption reports
june.track({
  userId: "user-123",
  event: "Feature Activated",
  properties: { feature: "api-access" },
});

Server-Side Middleware Pattern

// Express middleware for automatic route tracking
import { getJune } from "./lib/june";

function juneTrackingMiddleware(req, res, next) {
  const start = Date.now();

  res.on("finish", () => {
    if (req.user) {
      getJune().track({
        userId: req.user.id,
        event: "API Request",
        properties: {
          method: req.method,
          path: req.route?.path || req.path,
          statusCode: res.statusCode,
          durationMs: Date.now() - start,
        },
      });
    }
  });

  next();
}

Best Practices

  • Always call group() after identify() to link users to companies. June's B2B reports (company-level retention, feature adoption per company) depend on this association.
  • Use June's recommended event names (Signed Up, Invite Sent, Feature Activated) to take advantage of auto-generated reports instead of building custom dashboards from scratch.
  • Batch server-side events and call june.flush() before process exit (e.g., in serverless function teardown) to avoid losing events.

Common Pitfalls

  • Omitting the group() call means company-level analytics will be empty. This is the most common integration mistake, especially when migrating from a user-only analytics tool.
  • Sending high-cardinality property values (e.g., full URLs, unique IDs) as event properties pollutes reports. Use normalized categories instead (e.g., route pattern /users/:id rather than /users/12345).

Anti-Patterns

Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add analytics-services-skills

Get CLI access →