Official PostHog integration for Ruby on Rails applications. Automatically track exceptions, instrument background jobs, and capture user analytics.
- 🚨 Automatic exception tracking - Captures unhandled and rescued exceptions
- 🔄 ActiveJob instrumentation - Tracks background job exceptions
- 👤 User context - Automatically associates exceptions with the current user
- 🎯 Smart filtering - Excludes common Rails exceptions (404s, etc.) by default
- 📊 Rails 7.0+ error reporter - Integrates with Rails' built-in error reporting
- ⚙️ Highly configurable - Customize what gets tracked
Add to your Gemfile:
gem 'posthog-ruby'
gem 'posthog-rails'Then run:
bundle installNote: posthog-rails depends on posthog-ruby, but it's recommended to explicitly include both gems in your Gemfile for clarity.
Run the install generator to create the PostHog initializer:
rails generate posthog:installThis will create config/initializers/posthog.rb with sensible defaults and documentation.
PostHog.init creates a single client instance used across your app. Avoid creating multiple PostHog::Client instances with the same API key — it can cause dropped events.
The generated initializer at config/initializers/posthog.rb includes all available options:
# 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.capture_user_context = true # Include user info in exceptions
config.current_user_method = :current_user # Method to get current user
config.user_id_method = nil # Method to get ID from user (auto-detect)
# Add additional exceptions to ignore
config.excluded_exceptions = ['MyCustomError']
end
# Core PostHog client initialization
PostHog.init do |config|
# Required: Your PostHog API key
config.api_key = ENV['POSTHOG_API_KEY']
# Optional: Your PostHog instance URL
config.host = 'https://us.i.posthog.com' # or https://eu.i.posthog.com
# Optional: Personal API key for feature flags
config.personal_api_key = ENV['POSTHOG_PERSONAL_API_KEY']
# Error callback
config.on_error = proc { |status, msg|
Rails.logger.error("PostHog error: #{msg}")
}
endYou can also configure Rails options directly:
PostHog::Rails.config.auto_capture_exceptions = trueThe recommended approach is to use environment variables:
# .env
POSTHOG_API_KEY=your_project_api_key
POSTHOG_PERSONAL_API_KEY=your_personal_api_key # Optional, for feature flagsWhen auto_capture_exceptions is enabled, exceptions are automatically captured:
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
# Any exception here is automatically captured
end
endTrack custom events anywhere in your Rails app:
# Track an event
PostHog.capture(
distinct_id: current_user.id,
event: 'post_created',
properties: { title: @post.title }
)
# Identify a user
PostHog.identify(
distinct_id: current_user.id,
properties: {
email: current_user.email,
plan: current_user.plan
}
)
# Track an exception manually
PostHog.capture_exception(
exception,
current_user.id,
{ custom_property: 'value' }
)When auto_instrument_active_job is enabled, ActiveJob exceptions are automatically captured:
class EmailJob < ApplicationJob
def perform(user_id)
user = User.find(user_id)
UserMailer.welcome(user).deliver_now
# Exceptions are automatically captured with job context
end
endBy default, PostHog tries to extract a distinct_id from job arguments by looking for a user_id key in hash arguments:
class ProcessOrderJob < ApplicationJob
def perform(order_id, options = {})
# PostHog will automatically use options[:user_id] or options['user_id']
# as the distinct_id if present
end
end
# Call with user context
ProcessOrderJob.perform_later(order.id, user_id: current_user.id)For more control, use the posthog_distinct_id class method to define exactly how to extract the user's distinct ID from your job arguments:
class SendWelcomeEmailJob < ApplicationJob
posthog_distinct_id ->(user, options) { user.id }
def perform(user, options = {})
UserMailer.welcome(user).deliver_now
end
endYou can also use a block:
class ProcessOrderJob < ApplicationJob
posthog_distinct_id do |order, notify_user_id|
notify_user_id
end
def perform(order, notify_user_id)
# Process the order...
end
endThe proc/block receives the same arguments as perform, so you can extract the distinct ID however makes sense for your job.
Note: Currently only ActiveJob is supported. Support for other job runners (Sidekiq, Resque, Good Job, etc.) is planned for future releases. Contributions are welcome!
Use feature flags in your Rails app:
class PostsController < ApplicationController
def show
if PostHog.is_feature_enabled('new-post-design', current_user.id)
render 'posts/show_new'
else
render 'posts/show'
end
end
endPostHog integrates with Rails' built-in error reporting:
# These errors are automatically sent to PostHog
Rails.error.handle do
# Code that might raise an error
end
Rails.error.record(exception, context: { user_id: current_user.id })PostHog will automatically extract the user's distinct ID from either user_id or distinct_id in the context hash (checking user_id first). Any other context keys are included as properties on the exception event.
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
String | required | Your PostHog project API key |
host |
String | https://us.i.posthog.com |
PostHog instance URL |
personal_api_key |
String | nil |
For feature flag evaluation |
max_queue_size |
Integer | 10000 |
Max events to queue |
test_mode |
Boolean | false |
Don't send events (for testing) |
on_error |
Proc | nil |
Error callback |
feature_flags_polling_interval |
Integer | 30 |
Seconds between flag polls |
Configure these via PostHog::Rails.configure or PostHog::Rails.config:
| Option | Type | Default | Description |
|---|---|---|---|
auto_capture_exceptions |
Boolean | false |
Automatically capture exceptions |
report_rescued_exceptions |
Boolean | false |
Report exceptions Rails rescues |
auto_instrument_active_job |
Boolean | false |
Instrument ActiveJob |
capture_user_context |
Boolean | true |
Include user info |
current_user_method |
Symbol | :current_user |
Controller method for user |
user_id_method |
Symbol | nil |
Method to extract ID from user object (auto-detect if nil) |
excluded_exceptions |
Array | [] |
Additional exceptions to ignore |
auto_capture_exceptions - Master switch for all automatic error tracking (default: false)
- When
true: All exceptions are automatically captured and sent to PostHog - When
false(default): No automatic error tracking (you must manually callPostHog.capture_exception) - Use case: Enable to get automatic error tracking
report_rescued_exceptions - Control exceptions that Rails handles gracefully (default: false)
- When
true: Capture exceptions that Rails rescues and shows error pages for (404s, 500s, etc.) - When
false(default): Only capture truly unhandled exceptions that crash your app - Use case: Enable along with
auto_capture_exceptionsfor complete error visibility
Example:
# Scenario: User visits /posts/999999 (post doesn't exist)
def show
@post = Post.find(params[:id]) # Raises ActiveRecord::RecordNotFound
end| Configuration | Result |
|---|---|
auto_capture_exceptions = truereport_rescued_exceptions = true |
✅ Exception captured |
auto_capture_exceptions = truereport_rescued_exceptions = false |
❌ Not captured (Rails rescued it) |
auto_capture_exceptions = false |
❌ Not captured (automatic tracking disabled, this is the default) |
Recommendation: Enable both options to get complete visibility into all errors. Set report_rescued_exceptions = false if you only want to track critical crashes.
The following exceptions are not reported by default (common 4xx errors):
AbstractController::ActionNotFoundActionController::BadRequestActionController::InvalidAuthenticityTokenActionController::InvalidCrossOriginRequestActionController::MethodNotAllowedActionController::NotImplementedActionController::ParameterMissingActionController::RoutingErrorActionController::UnknownFormatActionController::UnknownHttpMethodActionDispatch::Http::Parameters::ParseErrorActiveRecord::RecordNotFoundActiveRecord::RecordNotUnique
You can add more with PostHog::Rails.config.excluded_exceptions = ['MyException'].
PostHog Rails automatically captures user information from your controllers:
class ApplicationController < ActionController::Base
# PostHog will automatically call this method
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
endIf your user method has a different name, configure it:
PostHog::Rails.config.current_user_method = :logged_in_userBy default, PostHog Rails auto-detects the user's distinct ID by trying these methods in order:
posthog_distinct_id- Define this on your User model for full controldistinct_id- Common analytics conventionid- Standard ActiveRecord primary keypk- Primary key aliasuuid- For UUID-based primary keys
Option 1: Configure a specific method
# config/initializers/posthog.rb
PostHog::Rails.config.user_id_method = :email # or :external_id, :customer_id, etc.Option 2: Define a method on your User model
class User < ApplicationRecord
def posthog_distinct_id
# Custom logic for your distinct ID
"user_#{id}" # or external_id, or any unique identifier
end
endThis approach is useful when you want to:
- Use a different identifier than the database ID (e.g.,
external_id) - Prefix IDs to distinguish user types
- Use composite identifiers
PostHog Rails automatically filters sensitive parameters:
passwordpassword_confirmationtokensecretapi_keyauthenticity_token
Long parameter values are also truncated to 10,000 characters.
In your test environment, you can disable PostHog or use test mode:
# config/environments/test.rb
PostHog.init do |config|
config.test_mode = true # Events are queued but not sent
endOr in your tests:
# spec/rails_helper.rb
RSpec.configure do |config|
config.before(:each) do
allow(PostHog).to receive(:capture)
end
endTo run tests:
cd posthog-rails
bundle install
bundle exec rspecPostHog Rails uses the following components:
- Railtie - Hooks into Rails initialization
- Middleware - Two middleware components capture exceptions:
RescuedExceptionInterceptor- Catches rescued exceptionsCaptureExceptions- Reports all exceptions to PostHog
- ActiveJob - Prepends exception handling to
perform_now - Error Subscriber - Integrates with Rails 7.0+ error reporter
-
Verify PostHog is initialized:
Rails.console > PostHog.initialized? => true
-
Check your excluded exceptions list
-
Verify middleware is installed:
Rails.application.middleware
- Verify
current_user_methodmatches your controller method - Check that the user object responds to one of:
posthog_distinct_id,distinct_id,id,pk, oruuid - If using a custom identifier, set
PostHog::Rails.config.user_id_method = :your_method - Enable logging to see what's being captured
Ensure you've set personal_api_key:
config.personal_api_key = ENV['POSTHOG_PERSONAL_API_KEY']See the main PostHog Ruby repository for contribution guidelines.
MIT License. See LICENSE for details.