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')
ortrack('Filter Applied')
Backend: Call
sendAnalytics('Todo Created', userId)
orsendAnalytics('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.
Other reference:
complete-analytics-setup-in-atlassian-forge