Skip to Content

Custom Events

A page view is one kind of thing that happens on a site. Everything else — a sign-up, a purchase, a video watched to the end — you record by calling pushbroom.event().

window.pushbroom?.event('Signed Up', { plan: 'solo' })

That is the entire API.

Guard the call

The ?. is not decoration. The tracking script is loaded async defer, so it does not block your page and has not necessarily arrived by the time your code runs. window.pushbroom?.event(…) does nothing on a page where the script has not loaded yet. window.pushbroom.event(…) throws.

You do not have to wait for anything else. An event fired in the first moments of a page — before Pushbroom has finished setting up — is held and sent as soon as the visit it belongs to exists. A conversion on a fast page is the event most likely to happen early and the one you can least afford to lose, so it is the case this is built around.

Events fired as the page is leaving are sent too. A call in a form’s submit handler survives the navigation that follows it.

form.addEventListener('submit', () => {
  window.pushbroom?.event('Signed Up', { plan: 'solo' })
})

The type

The first argument names the event, and the name is yours to pick. It belongs to your site and nowhere else: Signed Up on your site and Signed Up on somebody else’s are two different things, and nothing is pooled between sites.

Spaces are fine. Write the name you want to read on a chart, because that is where you are going to read it.

Two rules. It has to be a string, and it cannot be empty. A call that breaks either is ignored instead of recorded. An event class named undefined would be written once and then be part of your data for good.

Pick your names and then leave them alone. A rename is a new class; the old one keeps the history it already has.

The data

The second argument is optional. When you pass it, it is a flat object:

window.pushbroom?.event('Purchase', {
  plan: 'team',
  seats: 12,
  currency: 'USD'
})

Every key becomes something you can filter and group by. There is no set of custom-dimension slots to register in advance and no waiting period — a key you have never sent before shows up once there is data carrying it.

Keep values short. Strings and numbers, the kind of thing that belongs on an axis or in a filter.

Nested objects are not supported, and are not planned. { plan: { name: 'team' } } will not do what you want. Flatten it yourself:

window.pushbroom?.event('Purchase', { plan_name: 'team', plan_seats: 12 })

That is the shape you would have had to filter on anyway.

Your keys are recorded in your site’s own namespace, so they cannot collide with Pushbroom’s. Sending url records your url next to the one Pushbroom already recorded, rather than replacing it.

When nothing is sent

event() does nothing at all if the visitor has opted out, or if their browser is sending Global Privacy Control. That is not an error, it does not throw, and you do not have to check for it. See opting out.

Context you already have

If you know something when the page renders — which campaign brought the visitor, which variant they were served — put it on the element instead of calling event(). See custom data.

Last updated 2026.09.14