Skip to content

Autocapture for Phoenix LiveView #66

@neilberkman

Description

@neilberkman

Congrats on the 2.0 release! The SDK looks great. We're evaluating PostHog to replace our current analytics setup, but have a pain point we'd like to solve first.

The Problem

The PostHog JS SDK looks like it has great autocapture for client-side events. Phoenix LiveView apps handle most interactions server-side via handle_event/3 callbacks, which are invisible to client-side autocapture. This means we need manual PostHog.capture() calls for every event, making it easy to miss tracking new features—which is our biggest pain point with our current setup.

The Opportunity

LiveView provides lifecycle hooks that can intercept all handle_event/3 calls. Event names are already semantic ("create_account", "delete_deal"), and user context is always available in the socket. This is actually better than DOM events for autocapture.

Proposed Approach

Add optional autocapture via on_mount hook:

defmodule MyAppWeb.SomeLive do
  use MyAppWeb, :live_view
  on_mount {PostHog.LiveView, :autocapture}

  # All handle_event calls now auto-captured
  def handle_event("create_account", params, socket) do
    {:ok, account} = Accounts.create_account(params)
    {:noreply, socket}
  end
end

What gets captured automatically:

  • Event name
  • User ID from socket assigns
  • View name and live_action
  • Sanitized params

Smart filtering to reduce noise:

# Ignore by default: validate, change, keyup, focus, paginate
# Always capture: create, delete, update, save, submit

Optional property enrichment for complex cases:

def handle_event("create_account", params, socket) do
  {:ok, account} = Accounts.create_account(params)

  # Enrich with computed properties
  socket = PostHog.enrich_properties(socket, %{
    method: if(account.fast_add, do: "fast_add", else: "full"),
    stage: format_stage(account.stage)
  })

  {:noreply, socket}
end

Implementation

The core is straightforward—attach to LiveView's :handle_event hook:

def on_mount(:autocapture, _params, _session, socket) do
  socket = attach_hook(socket, :posthog, :handle_event, fn event, params, socket ->
    if should_capture?(event, socket) do
      properties = build_properties(params, socket)
      user_id = get_in(socket.assigns, [:current_user, :id])
      PostHog.capture(event, %{distinct_id: user_id, properties: properties})
    end
    {:cont, socket}
  end)

  {:cont, socket}
end

Impact

For our app (770 lines of analytics code, 49 tracked events):

  • Simple events: zero code needed (currently 150 lines)
  • Medium complexity: one-line enrichment (currently 320 lines)
  • Complex events: custom enrichment helper (currently 300 lines)

Estimated 50-70% reduction in analytics boilerplate.

Questions

  1. Is this something that would fit in the SDK?
  2. Any concerns about the approach?
  3. Would you prefer this as a separate package?

We're considering building this either way, but wanted to check if you had thoughts on design or would be interested in collaborating.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions