Skip to main content

Getting started guide: Implementing Customer health metrics as an Atlassian Marketplace App

Get Forge analytics working in 10 minutes - everything else can wait

Kate Caldecott avatar
Written by Kate Caldecott
Updated this week

Summary

A cheap and cheerful analytics implementation that:

  • ✅ Tracks user actions without violating Forge privacy policies

  • ✅ Works with any analytics provider (Accoil, Segment, etc.)

  • ✅ Can be tested locally without sending real data

  • Protects your "Runs on Atlassian" status by preventing End User Data (EUD) egress

  • ✅ Limits PII transmission to keep you compliant

Note: This is the minimal viable implementation. For production apps with high volume or complex requirements, see our Complete Analytics Setup for the full queue-based architecture we recommend.

Prerequisites

  • A working Forge app

  • An analytics API key (we'll use Accoil in examples)

  • 10 minutes

The Essential Pattern

Frontend Events → Backend Resolver → Analytics Provider ↑ Backend Events

Key Rule: Frontend NEVER talks directly to analytics. Everything goes through your backend to prevent End User Data (EUD) transmission, which would jeopardize your "Runs on Atlassian" vendor status.

Step 1: Update Your Manifest (1 minutes)

Add these permissions to manifest.yml:

YAML

permissions: external: fetch: backend: - address: "in.accoil.com" # or your analytics provider category: analytics inScopeEUD: false # CRITICAL: Protects "Runs on Atlassian" status

Step 2: Create the Backend Dispatcher (3 minutes)

Create src/analytics/dispatcher.js:

JavaScript

import { fetch } from '@forge/api'; // Main function that handles all analytics calls export const sendAnalytics = async (eventName, userId) => { // Skip in debug mode if (process.env.ANALYTICS_DEBUG === 'true') { console.log(`[Analytics Debug] Would send: ${eventName} for user $D`); return; } // Send to your analytics provider await fetch('https://in.accoil.com/v1/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ event: eventName, user_id: userId, api_key: process.env.ANALYTICS_API_KEY, timestamp: Date.now() }) }); };

Step 3: Add the Resolver (2 minutes)

In your main resolver file (usually src/index.js):

JavaScript

import { sendAnalytics } from './analytics/dispatcher'; resolver.define('track-event', async ({ payload, context }) => { const userId = context.accountId; // Atlassian user ID await sendAnalytics(payload.event, userId); });

Step 4: Track Events (2 minutes)

Frontend Tracking

Create static/spa/src/analytics.js:

JavaScript

import { invoke } from '@forge/bridge'; export const track = (eventName) => { invoke('track-event', { event: eventName }); };

Use it in your React components:

JavaScript

import { track } from './analytics'; // In your component const handleSearch = (query) => { if (query.length > 2) { track('Search Performed'); } // ... rest of your search logic };

Backend Tracking

Track directly from your resolvers for server-side events:

JavaScript

// In your main resolver (src/index.js) import { sendAnalytics } from './analytics/dispatcher'; resolver.define('create-todo', async ({ payload, context }) => { // Your business logic const todo = await createTodo(payload); // Track the backend event const userId = context.accountId; await sendAnalytics('Todo Created', userId); return todo; });

Step 5: Configure and Test (2 minute)

Set your API key:

Bash

forge variables set ANALYTICS_API_KEY your_key_here

Enable debug mode for testing:

Bash

forge variables set ANALYTICS_DEBUG true

Deploy and test:

Bash

forge deploy forge logs --tail

You should see:

[Analytics Debug] Would send: Search Performed for user 123456789 [Analytics Debug] Would send: Todo Created for user 123456789

That's It! 🎉

You now have working, analytics tracking both frontend interactions and backend operations. Everything else is optimization.

What's Next?

Need to track more events? Focus on meaningful business events:

  • Frontend: Call track('Search Performed') or track('Filter Applied')

  • Backend: Call sendAnalytics('Todo Created', userId) or sendAnalytics('User Invited', userId)

Want to reduce costs / GDPR compliance? Use the account ID instead of user ID:

JavaScript

const userId = context.cloudId; // Instance ID instead of user ID

Ready for production? Turn off debug mode:

Bash

forge variables unset ANALYTICS_DEBUG

Common Gotchas

  • Don't send user emails, names, or any PII

  • Don't track from frontend without going through resolver

  • Don't forget to set inScopeEUD: false in manifest

  • Don't track generic interactions like "clicked" or "viewed" - focus on business outcomes.

Did this answer your question?