Roadmap

A list of ideas, not a commitment. Nothing below the "Ideas" line is planned or scheduled.

Implemented

  • Feed — chronological event list with search, category filter, and a favorites-only toggle. Filters live in the URL, so a bookmarked /feed?category=errors&favorites=true renders filtered on first paint.
  • Charts — per-category event counts over a configurable window.
  • Insights — KPI cards, upserted on (project, title).
  • Playground — send a real event with a real key against the real endpoint.
  • Settings — rename/delete a project, manage keys and notifications.
  • External user ids — events carry a user_id from the calling application.
  • Event favorites and deletion, and insight deletion.
  • Responsive layout with a collapsible sidebar.
  • Multi-tenancy — projects, events, insights, and keys all hang off an organization; every route is scoped to it, and another tenant's resources 404 rather than 403.
  • Per-organization API keys — created from the dashboard, sha256-hashed at rest, shown once, revocable, with last_used_at tracking.
  • Authenticated reads — GET /api/events, /api/projects, and /api/charts require a key.
  • Live feed over SSE — Postgres LISTEN/NOTIFY per project, so a push reaches an open feed across serverless instances instead of waiting on a timer.
  • Keyset pagination — microsecond-precise cursors that neither skip nor repeat rows when callers supply their own created_at.
  • ntfy push — multiple destinations per project, self-hosted servers supported, credentials encrypted at rest, per-destination health and a test button. Only notify: true events fan out.
  • Incoming webhooks — per-project /hook/<token> URLs that create events with no API key, ntfy-style (curl -d "Backup finished" <url>), with fields from JSON, query string or headers.
  • Outgoing webhooks — notify: true events delivered as signed JSON (Standard Webhooks), Slack or Discord, with 3 in-memory retries and an SSRF guard on every destination URL.
  • Web push — native browser and phone notifications for notify: true events, per project and per device, installable on iOS from the Home Screen.
  • Export / import over HTTP — GET /api/export and POST /api/import, transactional, validated before the first write, ids remapped on the way in.
  • A remote CLI — butters ( getbutters-cli ) talks only to the API, so demo and imported data go through the same scoping and validation as live traffic.
  • Event retention — events older than the plan's window (90 days on Base, 365 on Plus) are deleted by a daily job, and straight away on a downgrade.
  • Publishable keys — a pk_ key scoped to one project with optional allowed origins, CORS, and per-key rate limiting, so a browser can send events directly. See Publishable Keys .
  • People — a People tab on every project, built from user_id, with POST /api/identify to set properties on a person. See People and identify .
  • JavaScript SDK — @getbutters/js on npm and jsDelivr: track, identify, reset and opt-in pageviews from the browser with a publishable key. See JavaScript SDK .

Known gaps

Small, real, and worth closing before anything on the Ideas list.

  • CLI `--tags`. push cannot set tags; the API can.
  • No poll fallback on the feed. The live feed is SSE-only. A feed that misses a NOTIFY shows stale data until reload, and on the Cloudflare adapter, which cannot hold a LISTEN connection, that is every feed.
  • Webhook delivery isn't durable. Retries live in memory; a restart mid-retry loses the delivery, and the Settings row's last error is the only record. A small delivery log or queue would fix both.
  • SSRF guard doesn't pin the connection. The host guard resolves the name, then the request resolves it again, so a short-TTL name can answer differently between the two.
  • Incoming hooks drop unknown JSON. A service posting its native payload (Stripe, GitHub) gets a 201 and a bare "Webhook" event. Keeping the body as metadata would make pointing a service straight at a hook useful.
  • ntfy drops event tags. Tags are stored as key-value pairs but sent as if they were a list, so no tags reach ntfy.
  • Slack shows Markdown literally. A description with **bold** arrives in Slack with the asterisks intact; Slack wants *bold*.
  • Push survives sign-out. Signing out leaves the browser's push subscription in place, so a shared computer keeps getting notifications.
  • Stale CLI demo table. The Demo Scenarios table in the CLI docs no longer matches the CLI.

Ideas

Summary dashboard

A project home page with aggregated KPIs (daily active, monthly active, signups today, all users), a line chart of daily actives, and inline metric cards.

Users section

A dedicated area listing identified people: online, recent, power, active, drifting. Builds on the People tab.

User profiles

Per-user pages: first seen, last seen, sessions, average session time, a GitHub-style activity heatmap, most-used features, properties, and an event journey timeline.

Feature usage analytics

Adoption and usage per feature — daily/monthly users, adoption percentage, power users, weekly usage bars.

Onboarding flow

A getting-started path for a new project: create project, mint a key, publish a first event, see it land.

Category management

Add, rename, and delete categories from the UI. They are only ever auto-created from events today.

Email notifications and per-category routing

ntfy, webhooks and web push cover real-time alerts, but every destination gets every notify: true event. Routing by category (billing to Slack, errors to push) and daily or weekly email digests would cover the rest.

Event detail view and comments

Click an event for full detail: raw JSON, all tags, the person behind user_id, and neighbouring events in time, with a team comment thread underneath. The comment count shows on the feed card so a discussion is visible without opening the event. Notifications and @mentions on comments come later.

Retention and archiving

Plan-based retention is in place. Still open: archival to object storage before deletion, a per-project window, and showing the window in the dashboard.

Rate limiting and quotas

Monthly event quotas per plan are enforced, and publishable keys are rate limited per key and client IP. Secret keys still have no per-key or per-IP limit, so a leaked secret key can burn a month's quota in minutes.

Key scoping

Secret keys are organization-wide. Publishable keys are project-scoped and write-only; a read-only key is still just an idea.

Insight increment

Let an insight take {"$inc": n} to add to or subtract from a card atomically. Today an insight update only replaces the value, so a live counter (signups today, queue depth) needs a read-then-write the caller can race.

Feedback widget

A drop-in script and button that posts what a visitor types into a project as an event. It needs nothing new on the server: publishable keys and CORS already do the work, and it could ship inside @getbutters/js.

Richer Markdown in descriptions

Italics, inline code and code blocks in event descriptions, not just **bold** and links. Error events with stack traces would read far better.

Toward product analytics

The direction: grow toward more product analytics without replacing what's here. The feed, notify, insights, charts and the notification fan-out stay the core of the product. Everything below reads from the same events table and adds views, not a new ingestion model. Explicit, human-meaningful events remain the default; anything captured automatically is opt-in per project.

Ordered so each phase unlocks the next. Several items above (Users section, User profiles, Feature usage analytics, Key scoping, Retention, Rate limiting) slot into these phases rather than standing alone.

Phase 1: identity (shipped)

The foundation for everything after it.

  • Publishable, write-only keys (shipped). A browser or mobile client gets a key that can only send events and identify people for one project.
  • A small JS SDK (shipped). track(), identify(), reset(), and optional pageview capture, off by default.
  • People (shipped). A people list built from user_id, with properties set by identify. Powers *Users section* and *User profiles*, both still open.

Phase 2: asking questions

  • Trends with breakdowns. Counts over time split by tag, metadata field or person property, not only by category.
  • Funnels. Ordered steps (signup, first project, first event) with conversion and drop-off between them.
  • Retention. Of the people who did X in week N, how many came back.
  • Cohorts. Saved groups of people, usable as a filter everywhere, including the feed.

Phase 3: alerts on patterns

Builds on the existing ntfy, webhook and push fan-out.

  • Threshold alerts. "More than 20 errors in 10 minutes."
  • Absence alerts. "No backup event in 25 hours." A dead-man's switch for cron jobs and deploys; the most Butters-shaped feature on this list.

Phase 4: dashboards and power tools

  • Dashboards. Pin charts, funnels and insight cards to a saved board. (Extends *Summary dashboard*.)
  • Read-only SQL over your own events, scoped to the organization, with timeouts and row limits.
  • Pipelines out. Forward events to a warehouse or another tool, reusing the outgoing webhook signing.

Later, maybe

Feature flags, experiments, surveys and web analytics are each a product of their own; revisit once phases 1 and 2 are in use.

Also parked for now: server and framework SDKs (Node, Python, Next.js, Vue) beyond the browser SDK, and a Zapier (or Bubble) integration as a no-code on-ramp next to the webhooks.

Deliberately not planned: session replay. Heavy on storage, privacy and SDK work, and unrelated to what the feed is for.

What gets urgent first

The browser SDK multiplies event volume. It's worth measuring whether Postgres keeps up or the events store needs a columnar option (ClickHouse, TimescaleDB).