Once your PostHog instance is up and running, the next step is to start sending events.
We recommend starting with autocapture for your web app as it's the quickest way to get set up, gives you full coverage, and avoids manually adding custom events. On top you can add custom events to track the most important events.
Tip: Autocapture sends lots of events. We provide a generous free tier but as you scale you might switch to mainly using custom events on the areas of the product that are more stable and tune autocapture to your needs.
1. Setup autocapture
When you call posthog.init
the PostHog JS library begins automatically capturing user events:
- Page views, including the URL
- Autocaptured events, such as any click, change of input, or submission associated with
a
,button
,form
,input
,select
,textarea
, andlabel
tags
Hiding certain elements
PostHog puts significant effort into ensuring it doesn't capture sensitive data from your website.
However, if there are specific elements you want to ensure aren't captured, you can add the ph-no-capture
class name.
<input class='ph-no-capture'><!-- Input with sensitive user info --></input>
Tip: Including the
ph-no-capture
class will also exclude elements from being shown in session recordings.
2. Capture custom events
Setting up autocapture is a great way to get started, but typically when integrating tracking into your product, you'll want to send additional events for when specific things occur.
On the client-side, we can use the capture
method, with the first argument being the name of the event you want to track.
posthog.capture('User signed up')
Tip: We recommend adding custom events for the most important actions in your product that are unlikely to change, such as user sign-ups, purchases, and when features are used. While you can name your events however you'd like, we typically recommend using a '[object][verb]' format, where '[object]' is whatever entity the action relates to, and the '[verb]' is the action itself. Some examples of this could include:
Project created
,User signed up
, orInvite sent
.
At first, it may seem somewhat unnecessary to send these custom events when we already have autocapture setup, but these custom events are important for two main reasons:
- Sending custom properties on an event
- Keeping events consistent over time
Sending custom properties on an event
Suppose you're building an Saas app and want to track when a user purchases a plan. With autocapture, you'll already see Clicked button with text 'Purchase'
events, but these won't have any information on which plan the user purchased!
We include extra information on events by adding a second parameter in our capture call, which contains a map of custom property names and values.
posthog.capture('Plan purchased', {price: 1599,planId: 'XYZ12345',frequency: 'monthly',features: {'SSO': true,'Custom branding': true,'Custom domains': false,}})
Later, we can use these properties to filter events based on a particular planId, or to calculate the aggregate values of a property over time.
Tip: Always include more properties than you might need at first! There's no limit to the number of properties an event can have, and your future self will thank you once again when you need to access a property that you may not have thought you needed.
Keeping events consistent and reliable
A second reason why Custom events become important is for keeping tracking consistent and reliable.
When elements of your frontend change, this can affect how autocapture events show up in PostHog.
Continuing with the example above, if we changed the text for our add to cart button to just 'Add', our autocapure events would begin showing up as Clicked button with text 'Add'
instead of what they had previously been.
While combining these two events using Actions to fix this drift is possible, manually track these high-value actions with custom events is far more reliable.
3. Capture backend events
We've now covered sending events using autocapture and custom tracking, but up until now we've only been sending events from our frontend website. While for simple websites this is all you'll need to set up, if you're building a web or mobile app with a backend, we also highly recommend setting up tracking from your server in addition to your frontend.
There are several reasons why it's often preferable to send certain custom events from the server-side:
- More reliable delivery: As these events originate from your server, there's no way for them to get accidentally blocked by client-side ad-blockers
- More reliable data: You can fetch up-to-date information directly from your database or other services, which may not be readily available on the frontend
Our SDK pages contain information on installing PostHog on your specific platform. Once you have the library installed, you can send a capture event using the same capture
method as in posthog-js
.
client.capture({distinctId: 'distinct_id',event: 'Order created',properties: {orderId: '#0054'subtotal: 3599,customerName: 'Max Hedgehog',},})
The only major difference on the server side is that we must include a distinct_id
with every event.
Often, this will come in the form of a unique user ID and can be pulled from a session cookie when requests arrive from the client.
When should I send events from the server vs. the client?
In general, our guidance is to track events on both the frontend and backend whenever possible, to ensure maximum reliability and the most flexibility when analyzing your data.
That being said, there are certain events that we highly recommend tracking from the server-side specifically:
- Sign-up events: Given how high value these events are, you should also send a server-side even whenever possible
- C(R)UD events: This is a broad class of events, but generally speaking, whenever you receive an API request to create or update a specific resource within your application, it's useful to forward these to PostHog. We also recommend including context about the request itself in the event (latency, errors, any properties passed in the request payload, etc.)
- Backend jobs: PostHog is for more than just optimizing your frontend! It can often be useful to send events whenever backend jobs or workflows are kicked off, which allows you to analyze them within PostHog.
Tip: We recommend using different event names for your backend and frontend events to avoid the chance of duplicate counting. Often these should be the CRUD events themselves e.g.
User created
for the backend andUser signed up
for the frontend. Additionally, you can use thesource
property to filter for events from a specific source.
Event ingestion nuances
It's a priority for us that events are fully processed and saved as soon as possible. However, there is a class of events which we deliberately process with a slight delay. Specifically, an event is delayed by around a minute if it fits all of the following three conditions:
- isn't from an anonymous user (anonymous users are recognized by having the
distinct_id
the same as the$device_id
property) - isn't an
$identify
event (e.g. fromposthog.identify()
) - its
distinct_id
cannot be matched to an existing person
This delay mechanism is called the event buffer, and it materially improves handling of an edge case which could otherwise inflate unique user counts.
How does the event buffer help?
Starting with version 1.38.0, PostHog stores the person associated with an event inline with the event record. This greatly improves query performance, but because events are immutable, it also means that persons can't be merged retroactively. See this scenario where that's problematic:
- User visits signup page, in turn frontend captures anonymous
$pageview
for distinct IDXYZ
(anonymous distinct ID = device ID).
This event gets person IDA
. - User click signup button, initiating in a backend request, in turn frontend captures anonymous
$autocapture
(click) for distinct IDXYZ
.
This event gets person IDA
. - Signup request is processed in the backend, in turn backend captures identified signup for distinct ID
alice@example
.com.
OOPS! We haven't seenalice@example.com
before, so this event gets person IDB
. - Signup request finishes successfully, in turn frontend captures identified $identify aliasing distinct ID
XYZ
toalice@example.com
.
This event gets person IDA
.
Here, the event from step 3 got a new person ID B
, impacting unique users counts. If it were delayed just a bit and processed after the event from step 4, all events would get the expected person ID A
. This is exactly what the event buffer achieves.