|

Getting started with Analytics

Clone the source, configure PostgreSQL, sessions, and S3 storage, apply migrations, create the first admin, load demo data or add a site, install the script, and deploy.

Requirements

  • Node.js matching package.json engines (>=20.9.0). The app uses Next.js 16 and React 19.
  • npm (the repository ships a package-lock.json).
  • A PostgreSQL database you are allowed to migrate. Reports use PostgreSQL SQL directly, and the collector uses pg_advisory_xact_lock, so other databases are not supported.
  • S3-compatible object storage settings when NODE_ENV=production.

Live demo

A hosted demo runs at analytics.openship.org. The public site lists the demo dashboards, and the operator dashboard is at analytics.openship.org/dashboard. Its numbers come from generated demo traffic.

Install

git clone https://github.com/openshiporg/analytics.git
cd analytics
npm ci

npm install also works. The repository does not ship a .env.example, so create .env yourself.

Environment

features/keystone/index.ts refuses to start without DATABASE_URL and SESSION_SECRET, and rejects a session secret shorter than 32 characters. The five S3 values fall back to development placeholders outside production and are required when NODE_ENV=production.

DATABASE_URL=postgresql://user:password@127.0.0.1:5432/openfront_analytics
SESSION_SECRET=replace-with-at-least-32-random-characters   # openssl rand -hex 32

S3_BUCKET_NAME=your-bucket
S3_REGION=your-region
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-key
S3_ENDPOINT=https://your-s3-compatible-endpoint

SESSION_SECRET does more than sign dashboard sessions. The collector derives the daily visitor salt from it, and shared-link unlock tokens are signed with it. Changing it signs everyone out, invalidates unlocked shared-link cookies, and means the same visitor gets a new hash for the rest of that day.

Optional settings read by the code:

VariableUsed for
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROMPassword reset email. Without SMTP_HOST, the reset page says no mail transport is configured.
SMTP_STORE_LINKBase URL used in reset links
MAIL_USERIf it contains ethereal.email, the mailer logs a test preview link
PUBLIC_SIGNUPS_ALLOWEDSet to true to open /dashboard/signup
OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENSShared key and model for the dashboard AI chat (/api/completion); max tokens defaults to 4000
NEXT_PUBLIC_VIEW_ORDERDashboard field view ordering (generated by npm run migrate:gen)

Keep .env out of source control.

Migrations and local run

npm run dev

npm run dev runs keystone build --no-ui, applies the checked-in migrations with prisma migrate deploy, and starts Next.js. The repository has one migration, 20261025211115_initial_analytics. To change the schema, run npm run migrate:gen against a disposable database. It rebuilds the schema, regenerates the dashboard view order, and runs keystone prisma migrate dev. Commit model changes and their migrations together.

npm run dev applies migrations to whatever DATABASE_URL points at. Do not point it at a shared or production database.

Once running:

  • Public site: /
  • Operator dashboard: /dashboard
  • GraphQL API: /api/graphql
  • Tracker script: /assets/script.js
  • Collector: /api/track

First user

On an empty database, every page redirects to /dashboard/init. Create the first account there. It gets an Admin role with every permission turned on. Then sign in. Later users are added from the generic User list, or through /dashboard/signup if PUBLIC_SIGNUPS_ALLOWED=true. Give them a Role with the permissions they need.

Onboarding

The sidebar shows a Get started card to users with canManageOnboarding. It opens a setup dialog with three templates:

TemplateWhat it loads
Complete SetupStore settings, three sites, nine goals, three funnels, three segments, two shared links, and 60 days of traffic per site
Basic SetupStore settings and the Northwind Coffee site with 14 days of traffic
Custom SetupYour own JSON in the same shape as features/platform/onboarding/lib/seed.json

Each record is written through the ensureOnboardingRecord mutation. It reuses an existing record with the same natural key (for example the site domain) and never overwrites it, so setup can be run again. Traffic is generated in the browser and sent through importSiteSessions with stable keys, so a re-run skips visits it already imported. The seed marks two sites as public and gives one shared link a password; change or delete those on a real instance.

Add a real site

  1. Open /dashboard/platform/sites and create a site with a name, domain (a hostname such as example.com; subdomains are included), timezone (an IANA name), and currency (a three-letter code).
  2. Open the site's Settings tab and copy the tracking snippet. It looks like this:
<script defer src="https://your-analytics-host/assets/script.js" data-site-id="site_..."></script>
  1. Add it to the head of every page. Hits from localhost are skipped unless the script tag has data-allow-localhost="true".
  2. Visit the site and check the Realtime tab.

Deployment

Scripts in package.json:

ScriptEffect
npm run buildkeystone build --no-ui && next build; does not migrate
npm run migrateprisma migrate deploy
npm startnext start
npm run lintESLint
npm testNode test runner over tests/*.test.ts and tests/*.test.cjs

A typical release builds, applies migrations against a backed-up database, then starts:

npm run build
npm run migrate
npm start

The included railway.toml builds with keystone build --no-ui && next build and starts with npm run migrate && next start, because the build cannot reach an internal database. The README also has a Vercel deploy button. Either way, provision PostgreSQL and S3-compatible storage yourself and share the same database and SESSION_SECRET across all app processes. Put the app behind a proxy that sets X-Forwarded-For correctly, and ideally one that sets a country header such as cf-ipcountry or x-vercel-ip-country (see Integrations and boundaries).

On this page