Ruby on Rails

Contents

PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom event capture, feature flags, and automatic exception tracking.

This guide walks you through integrating PostHog into your Rails app using the posthog-rails gem.

Install with the wizard

Run this command in your project directory to add PostHog to your Rails project. The wizard detects your framework, installs the SDK, and sets up your first events.

Learn more

Or set it up manually by following the rest of this guide.

Features

  • Automatic exception tracking – Captures web request, streaming, Action Cable, and Rails runner failures
  • Active Job instrumentation – Tracks background job exceptions, optionally reports retry failures, and carries session and person IDs into queued jobs
  • User context – Automatically associates exceptions with the current user
  • Smart filtering – Excludes common Rails exceptions (404s, etc.) by default
  • Request context – Adds request metadata and optional PostHog tracing header identity/session context to captured events
  • Rails 7.0+ error reporter – Integrates with Rails' built-in error reporting
  • Log forwarding – Forwards Rails.logger output and optional structured Rails notification logs to PostHog Logs, correlated with request context (Ruby 3.3+)
  • Transactional emails – Sends emails from PostHog templates or content rendered by your app

Installation

Add both gems to your Gemfile:

Gemfile
gem 'posthog-ruby', '~> 3.0', require: 'posthog'
gem 'posthog-rails', '~> 3.0'

Then run:

Terminal
bundle install

Identifying users

Identifying users is required. Backend events need a distinct_id that matches the ID your frontend uses when calling posthog.identify(). Without this, backend events are orphaned — they can't be linked to frontend event captures, session replays, LLM traces, or error tracking.

See our guide on identifying users for how to set this up.

Generate the initializer

Run the install generator to create the PostHog initializer:

Terminal
rails generate posthog:install

This creates config/initializers/posthog.rb with sensible defaults and documentation.

Configuration

PostHog.init creates a single client instance used across your app. Avoid creating multiple PostHog::Client instances with the same API key, as this can cause dropped events and inconsistent behavior.

The generated initializer includes the most common options:

config/initializers/posthog.rb
# Rails-specific configuration
PostHog::Rails.configure do |config|
config.auto_capture_exceptions = true # Enable automatic exception capture (default: false)
config.report_rescued_exceptions = true # Report exceptions Rails rescues (default: false)
config.auto_instrument_active_job = true # Instrument background jobs (default: false)
config.use_tracing_headers = true # Use PostHog tracing headers for identity/session context (default: true)
config.capture_user_context = true # Include authenticated user info in exceptions (default: true)
config.current_user_method = :current_user # Method to get current user (default: :current_user)
config.user_id_method = nil # Method to get ID from user object (default: auto-detect)
# Add additional exceptions to ignore
config.excluded_exceptions = ['MyCustomError']
end
# Core PostHog client initialization
PostHog.init do |config|
# Required: Your PostHog project API key
config.api_key = '<ph_project_token>'
# Optional: Your PostHog instance URL
config.host = 'https://us.i.posthog.com'
# Optional: Secret key for local feature flag evaluation and remote config
config.secret_key = ENV["POSTHOG_SECRET_KEY"]
# Maximum number of events to queue before dropping (default: 10000)
config.max_queue_size = 10_000
# Send events synchronously on the calling thread (default: false)
config.sync_mode = false
# Feature flags polling interval in seconds (default: 30)
config.feature_flags_polling_interval = 30
# Feature flag request timeout in seconds (default: 3)
config.feature_flag_request_timeout_seconds = 3
# Error callback to detect misconfiguration
config.on_error = proc { |status, msg|
Rails.logger.error("PostHog error: #{msg}")
}
# Before-send callback to modify or drop events
config.before_send = proc { |event|
event[:properties] ||= {}
event[:properties]['environment'] = Rails.env
event
}
# Keep analytics events queued in test mode
config.test_mode = true if Rails.env.test?
end

You can find your project token and instance address in your project settings.

Tip: Use Rails.application.credentials to avoid hardcoding API keys. First, add your keys and then reference them in your initializer:

Terminal
rails credentials:edit
config/credentials.yml.enc
posthog:
api_key: <ph_project_token>
host: https://us.i.posthog.com
secret_key: phs_xxxxxxxxx
config/initializers/posthog.rb
config.api_key = Rails.application.credentials.posthog[:api_key]
config.host = Rails.application.credentials.posthog[:host]
config.secret_key = Rails.application.credentials.posthog[:secret_key]

Capturing events

Track custom events anywhere in your Rails app:

Ruby
PostHog.capture({
distinct_id: current_user.id,
event: 'post_created',
properties: { title: @post.title }
})

Identify a user and set their person properties:

Ruby
PostHog.identify({
distinct_id: current_user.id,
properties: {
email: current_user.email,
plan: current_user.plan
}
})

The Rails integration delegates methods like capture, identify, alias, group_identify, evaluate_flags, capture_exception, flush, and shutdown to the initialized PostHog::Client.

Request context

PostHog Rails automatically applies request-scoped context to events captured during web requests. Request metadata such as $current_url, $request_method, $request_path, $user_agent, and $ip is added to event properties.

When use_tracing_headers is enabled, PostHog tracing headers (X-PostHog-Distinct-Id and X-PostHog-Session-Id) are also used as default distinct_id and $session_id values. Explicit distinct_id and properties passed to PostHog.capture always take precedence.

If you're using PostHog JS on the frontend, configure tracing_headers for your Rails backend hostname so browser requests include the session and distinct ID headers.

Tracing headers are client-controlled analytics context, not authentication or authorization. Pass an authenticated distinct_id explicitly for security-sensitive server-side decisions.

Disable tracing header identity/session capture if you do not want client-supplied tracing headers used for server-side events. Request metadata is still captured:

Ruby
PostHog::Rails.config.use_tracing_headers = false

Trace context

PostHog Rails adds missing sessionId and posthogDistinctId attributes to an active OpenTelemetry SDK request span after it reads the request context. To also add these attributes to child spans, register the optional processor on your existing OpenTelemetry provider before requests start:

Ruby
require 'posthog/otel/context_span_processor'
OpenTelemetry.tracer_provider.add_span_processor(
PostHog::ContextSpanProcessor.new
)

This integration supports OpenTelemetry SDK 1.0.0 or later. Keep your existing provider, exporter, sampler, and processors. Put the OpenTelemetry request middleware outside the PostHog request middleware so the request span is active when PostHog reads the context. To send spans to PostHog, configure your existing OTLP exporter with the PostHog trace endpoint.

Existing sessionId and posthogDistinctId span attributes take precedence. Set authenticated application identity with posthogDistinctId. Missing IDs stay absent. Spans that already started keep their attributes after the request ends. Separate threads and fibers need explicit context transfer. With config.auto_instrument_active_job = true, Active Job copies the session and distinct IDs into queued jobs, so job spans get them too. The processor does not create a session, recording, trace provider, or exporter.

Logs

To set up PostHog Logs in your Rails app, follow the Ruby on Rails logs installation guide. The integration forwards Rails.logger output and optional structured controller, SQL, job, and mailer notification logs, correlated with PostHog identity and active OpenTelemetry traces. Requires Ruby 3.3+.

Error tracking

The integration supports controller reports, Active Job failures and retries, Action Cable, streaming responses, and Rails runner failures. See the Rails error tracking guide for setup, identity resolution, filtering, and examples.

Feature flags

Evaluate flags once for the current user, then read values from the returned snapshot:

Ruby
class PostsController < ApplicationController
def show
flags = PostHog.evaluate_flags(current_user.id)
if flags.enabled?('new-post-design')
render 'posts/show_new'
else
render 'posts/show'
end
end
end

For multivariate flags and experiments, use get_flag:

Ruby
flags = PostHog.evaluate_flags(current_user.id)
variant = flags.get_flag('checkout-experiment')
if variant == 'test'
# Do something differently
end

When capturing an event after branching on a flag, pass the same flags snapshot so the event includes the exact flag values used by your code:

Ruby
flags = PostHog.evaluate_flags(current_user.id)
PostHog.capture({
distinct_id: current_user.id,
event: 'checkout_started',
flags: flags.only_accessed
})

For local evaluation, configure secret_key in your initializer. See Ruby local evaluation for credentials, caching, and Puma/Unicorn setup.

Older single-flag methods are deprecated. See the migration reference.

Transactional emails

See send transactional emails with Ruby for Rails setup and examples using PostHog.emails.

Testing

In your test environment, keep analytics events queued with test mode:

config/environments/test.rb
PostHog.init do |config|
config.api_key = '<ph_project_token>'
config.test_mode = true
end

Stub feature flag definition fetches separately. For transactional emails, follow the email testing guide.

Or stub event capture in your specs:

spec/rails_helper.rb
RSpec.configure do |config|
config.before(:each) do
allow(PostHog).to receive(:capture)
end
end

Configuration reference

Core PostHog options

PostHog.init accepts Ruby SDK options as a hash. Use this form for options not supported by the initializer block:

Ruby
PostHog.init(
api_key: "<ph_project_token>",
host: "https://us.i.posthog.com",
secret_key: ENV.fetch("POSTHOG_SECRET_KEY"),
flush_interval_seconds: 2,
feature_flags_async_load: true
)

See asynchronous flag loading for startup behavior and flush/shutdown for timeout options.

Rails-specific options

Configure these via PostHog::Rails.configure or PostHog::Rails.config:

OptionTypeDefaultDescription
auto_capture_exceptionsBooleanfalseAutomatically capture exceptions.
report_rescued_exceptionsBooleanfalseReport exceptions Rails rescues.
auto_instrument_active_jobBooleanfalseCapture Active Job exceptions and propagate session and distinct IDs into queued jobs.
report_active_job_retriesBooleanfalseReport Active Job retry failures. Requires auto_instrument_active_job. See retry reporting.
excluded_exceptionsArray[]Additional exception class names to ignore.
use_tracing_headersBooleantrueUse X-PostHog-Distinct-Id and X-PostHog-Session-Id as request-scoped defaults.
capture_user_contextBooleantrueInclude authenticated user info in exceptions.
current_user_methodSymbol:current_userController or Action Cable connection method used to fetch the current user.
current_user_resolverProcnilResolve the current user for controller exception reports. Takes precedence over current_user_method. See user context.
user_id_methodSymbolnilMethod used to extract the distinct ID from the user object. Auto-detects when nil.

Troubleshooting

Exceptions not being captured

  1. Verify PostHog is initialized:

    Ruby
    Rails.console
    > PostHog.initialized?
    => true
  2. Check your excluded exceptions list.

  3. Verify middleware is installed:

    Ruby
    Rails.application.middleware

User context not working

  1. Verify current_user_method matches your controller method.
  2. Check that the user object responds to posthog_distinct_id, distinct_id, id, pk, or uuid. See user context to use a custom resolver.
  3. If using a custom identifier, set PostHog::Rails.config.user_id_method = :your_method.

Feature flags not working

Check your local evaluation configuration. With asynchronous loading, check PostHog.feature_flags_loaded? before expecting local results.

Next steps

For any technical questions for how to integrate specific PostHog features into Rails (such as analytics, feature flags, A/B testing, etc.), have a look at our Ruby SDK docs.

Still have questions?

Was this page useful?