Documentation
Everything you need to set up and use Bananalytics Analytics
Quick Start
Get Bananalytics running in 5 minutes with Docker.
git clone https://github.com/bananalytics-analytics/bananalytics.git
cd bananalytics/server
docker-compose up -ddocker exec -i server-postgres-1 psql -U bananalytics -d bananalytics \
< internal/storage/postgres/migrations/001_create_projects.up.sql
docker exec -i server-postgres-1 psql -U bananalytics -d bananalytics \
< internal/storage/postgres/migrations/002_create_events.up.sql
docker exec -i server-postgres-1 psql -U bananalytics -d bananalytics \
< internal/storage/postgres/migrations/003_add_key_hashes.up.sql
docker exec -i server-postgres-1 psql -U bananalytics -d bananalytics \
< internal/storage/postgres/migrations/004_add_geo.up.sqlcurl -X POST http://localhost:8080/v1/projects \
-H "Content-Type: application/json" \
-d '{"name":"My App"}'
# Returns: { "write_key": "rk_...", "secret_key": "sk_..." }AI Setup
Copy the prompt below and paste it into Claude Code, Cursor, Copilot, or any AI coding agent. It will integrate Bananalytics into your React Native app in one run.
Integrate Bananalytics analytics into this React Native app. Bananalytics is a self-hosted, privacy-first product analytics tool. Follow these steps exactly: ## 1. Install dependencies ...
How it works
- Copy the prompt above into Claude Code, Cursor, or any AI coding agent
- Replace YOUR_WRITE_KEY and YOUR_ENDPOINT with your actual values
- Answer the AI when it asks what events you want to track
- Done — the AI installs the SDK, adds tracking calls, and instruments your app
React Native SDK
Install the SDK in your React Native app.
npm install @bananalytics/react-native
npm install @react-native-async-storage/async-storageimport { Bananalytics } from '@bananalytics/react-native';
Bananalytics.init({
apiKey: 'rk_your_write_key',
endpoint: 'https://your-server.com',
debug: true,
});// Track custom events
Bananalytics.track('button_clicked', { button: 'signup' });
// Track screen views
Bananalytics.screen('HomeScreen');
// Identify users
Bananalytics.identify('user-123', { plan: 'pro' });
// Flush events immediately
await Bananalytics.flush();React Provider
import { BananalyticsProvider, useBananalytics, useTrackScreen } from '@bananalytics/react-native';
function App() {
return (
<BananalyticsProvider config={{ apiKey: 'rk_...', endpoint: '...' }}>
<HomeScreen />
</BananalyticsProvider>
);
}
function HomeScreen() {
useTrackScreen('HomeScreen');
const bananalytics = useBananalytics();
return (
<Button onPress={() => bananalytics.track('tapped')} title="Tap" />
);
}Configuration Options
| Option | Default | Description |
|---|---|---|
| apiKey | required | Write-only API key |
| endpoint | required | Backend URL |
| flushInterval | 30000 | Auto-flush interval (ms) |
| flushAt | 20 | Events before auto-flush |
| maxQueueSize | 1000 | Max events in memory |
| maxRetries | 3 | Retry attempts |
| debug | false | Console logging |
| trackAppLifecycle | true | Auto-track foreground/background |
| sessionTimeout | 1800000 | Session timeout (ms) |
Event Strategy
A good event strategy is the difference between a dashboard full of noise and one that drives decisions. Here is how to instrument your app to get the most out of Bananalytics.
Core Events You Should Track
These events power the dashboard features and give you a complete picture of user behavior.
Onboarding & Activation
Measure how users get from install to value. Build funnels to find where they drop off.
app_openedDistinguish first launch from returning users
first_open: boolean
signup_startedSee which auth methods convert best
method: 'email' | 'google' | 'apple'
signup_completedMeasure signup friction. Funnel: started → completed
method, time_to_complete_ms
onboarding_step_viewedFind which onboarding step loses users
step: number, step_name: string
onboarding_completedTrack activation rate
steps_completed: number
Core Product Usage
Track the actions that define your product's value. These power retention cohorts.
feature_usedSee which features drive retention
feature: string
content_viewedUnderstand what users engage with
content_id, content_type, source
search_performedDiscover unmet needs (zero-result searches)
query, results_count
item_createdMeasure creation activity as engagement signal
item_type, item_id
share_tappedTrack organic virality loops
content_type, share_method
Revenue & Conversion
Track the money path. Build funnels from browse to purchase to optimize conversion.
product_viewedTop of the purchase funnel
product_id, price, category
add_to_cartMid-funnel intent signal
product_id, quantity, price
checkout_startedHigh-intent moment — track abandonment
cart_value, item_count
purchase_completedRevenue tracking. Compare to checkout_started for drop-off
order_id, total, currency, items
subscription_startedSaaS conversion tracking
plan, price, trial: boolean
subscription_cancelledUnderstand churn reasons
plan, reason, days_active
Engagement & Retention Signals
These events feed your retention heatmap and help predict churn.
session_startedSession count per user = engagement health
(auto-tracked)
notification_receivedMeasure push notification effectiveness
type, campaign_id
notification_tappedTap rate = notification quality signal
type, campaign_id
rating_promptedOptimize when to ask for reviews
days_since_install
rating_submittedTrack app store rating health
stars, days_since_install
Errors & Friction
Track where users hit walls. These often reveal the biggest conversion opportunities.
error_occurredSurface bugs that affect real users
error_code, screen, message
payment_failedLost revenue you can recover
error_type, retry_count
form_abandonedFind the field that kills your form
form_name, last_field_filled
permission_deniedUsers refusing permissions = feature blockers
permission_type
Using Events with Dashboard Features
| Dashboard Feature | Events to Track | Insight You Get |
|---|---|---|
| Funnels | signup_started → signup_completed → first_purchase | Where users drop off in your conversion flow |
| Retention | Any recurring action (session_started, feature_used) | How many users come back on day 1, 7, 30 |
| Live View | All events in real-time | Verify tracking works, monitor launches & campaigns |
| Geography | All events (geo is extracted from IP) | Where your users are, localization priorities |
| Sessions | session_started + any user-identified events | Debug individual user journeys |
Best Practices
- Use past tense for event names —
purchase_completednotpurchase. It is clear that the action happened. - Use snake_case consistently — Bananalytics groups events by name. Mixed casing creates duplicates.
- Keep properties flat —
{ price: 49.99, currency: "USD" }not{ payment: { price: 49.99 } }. Easier to query. - Call
identify()early — As soon as the user logs in. This links anonymous events to a real user for session tracking. - Track screens with
screen()— It auto-createsscreen_viewevents, which powers the top events dashboard and retention. - Start with 10-15 events max — You can always add more. Too many events early on create noise and make dashboards hard to read.
// After user logs in
Bananalytics.identify('user-123', { plan: 'free' });
// Screen views (auto-tracked with useTrackScreen hook)
Bananalytics.screen('HomeScreen');
Bananalytics.screen('ProductScreen');
// Core conversion funnel
Bananalytics.track('product_viewed', {
product_id: 'prod_abc', price: 49.99, category: 'shoes'
});
Bananalytics.track('add_to_cart', {
product_id: 'prod_abc', quantity: 1, price: 49.99
});
Bananalytics.track('checkout_started', {
cart_value: 49.99, item_count: 1
});
Bananalytics.track('purchase_completed', {
order_id: 'ord_xyz', total: 49.99, currency: 'USD'
});
// Engagement signals
Bananalytics.track('search_performed', {
query: 'running shoes', results_count: 24
});
Bananalytics.track('share_tapped', {
content_type: 'product', share_method: 'instagram'
});
// Error tracking
Bananalytics.track('payment_failed', {
error_type: 'card_declined', retry_count: 0
});API Reference
Include your API key in every request:
curl -H "Authorization: Bearer sk_your_secret_key" http://localhost:8080/v1/query/eventsrk_* — Write Key (ingestion)sk_* — Secret Key (queries)Error Codes
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Invalid request body or parameters |
| 400 | VALIDATION_FAILED | Event validation failed |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 413 | PAYLOAD_TOO_LARGE | Request body exceeds 5MB |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL_ERROR | Server error |
Self-Hosting
Deploy on any VPS with Docker. Recommended: Hetzner CX22 (~$4/month).
# SSH into your server
ssh root@your-server-ip
# Install Docker
curl -fsSL https://get.docker.com | sh
# Clone and start
git clone https://github.com/bananalytics-analytics/bananalytics.git
cd bananalytics/server
# Set your domain (Caddy auto-provisions SSL)
echo "ROCHADE_DOMAIN=analytics.yourdomain.com" > .env
echo "ROCHADE_CORS_ORIGINS=https://yourdomain.com" >> .env
# Start everything (Postgres + Go server + Caddy HTTPS)
docker-compose up -dPoint your domain's DNS A-record to your server IP. Caddy handles SSL automatically via Let's Encrypt.
Configuration
| Variable | Default | Description |
|---|---|---|
| ROCHADE_PORT | 8080 | HTTP server port |
| ROCHADE_DB_DSN | required | PostgreSQL connection string |
| ROCHADE_LOG_LEVEL | info | debug, info, warn, error |
| ROCHADE_RATE_LIMIT_RPM | 1000 | Requests/min per API key |
| ROCHADE_IP_RATE_LIMIT_RPM | 300 | Requests/min per IP |
| ROCHADE_CORS_ORIGINS | * | Allowed origins |
| ROCHADE_DB_MAX_CONNS | 25 | Max DB connections |
| ROCHADE_GEOIP_DB | Path to GeoLite2-City.mmdb | |
| ROCHADE_DOMAIN | localhost | Domain for Caddy HTTPS |
Privacy & Compliance
Bananalytics is designed with privacy in mind. All data stays on your infrastructure — no third-party services, no data sharing.
- Self-hosted: Data never leaves your server
- Opt-out support: Built-in consent management in the SDK
- PII sanitization: Auto-strips email, phone, SSN from auto-captured events
- No cookies: Uses device storage, not browser cookies
- GDPR-friendly: You control the data, you handle deletion requests