10 minute Forge analytics guide
Get Forge analytics working in 10 minutes - everything else can wait
What You'll Build
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 EventsKey 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:
permissions:
external:
fetch:
backend:
- address: "in.accoil.com" # or your analytics provider
category: analytics
inScopeEUD: false # CRITICAL: Protects "Runs on Atlassian" statusStep 2: Create the Backend Dispatcher (3 minutes)
Create src/analytics/dispatcher.js:
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 ${userId}`);
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):
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:
import { invoke } from '@forge/bridge';
export const track = (eventName) => {
invoke('track-event', { event: eventName });
};Use it in your React components:
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:
// 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:
forge variables set ANALYTICS_API_KEY your_key_hereEnable debug mode for testing:
forge variables set ANALYTICS_DEBUG trueDeploy and test:
forge deploy
forge logs --tailYou should see:
[Analytics Debug] Would send: Search Performed for user 123456789
[Analytics Debug] Would send: Todo Created for user 123456789That'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:
const userId = context.cloudId; // Instance ID instead of user IDReady for production? Turn off debug mode:
forge variables unset ANALYTICS_DEBUGCommon 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: falsein manifest - ❌ Don't track generic interactions like "clicked" or "viewed" - focus on business outcomes
Time to first event: 10 minutes