Skip to content

JavaScript API

RoutingCat’s head partial, which npx routingcat push writes and your layout includes first in <head>, gives every page the routingcat object. Your server reports conversions through the HTTP events API, and can set RoutingCat’s cookies with the server helpers init writes.

The page’s routingcat object

The head partial gives every page that carries it window.routingcat. Its track() and identify() send from the page: they never throw, and send the visitor id themselves; they post straight to ingest.routingcat.com with your site’s public key, which RoutingCat accepts only from your site’s domains. A call made before RoutingCat’s post part has loaded is kept, and sent once it has. On a host your config doesn’t list, as on your machine, they log to the browser’s console instead.

routingcat.track()

routingcat.track(name: string, options?: TrackOptions): Promise<void>

Reports an event, such as a conversion, from the page. Its time is when it arrives, and its options are:

Option Type Default What it does
id string None The dedup key: an event sent again with the same name and id counts once. Generated when absent.
visitorId string The rc_vid cookie’s The visitor.
accountId string None Your app’s id for the account, when you have it: a record id, never an email address.
value number None A number. With currency, money in major units: 29.99 for $29.99. Without, a plain quantity.
currency string None The ISO 4217 code, in either case.
properties object None An object of at most 8,000 bytes of JSON, recorded with the event.
items LineItem[] None Line items: { id, quantity?, unitValue }, in the event’s currency, quantity 1 when absent.

routingcat.identify()

routingcat.identify(accountId: string): Promise<void>

Links the visitor to your app’s account id: your own record id, or the hex SHA-256 of the trimmed, lowercased email when there is no record.

routingcat.link(url: string | URL): string

The link with the visitor id, for a link to another of your domains, so that the visit continues there: RoutingCat’s script on that domain adopts the id. A link within your site comes back as it was. Add the domain with npx routingcat domains add.

routingcat.routingcatProperties

PostHog’s before_send hook: in your PostHog init, before_send: window.routingcat.routingcatProperties puts the visitor id and the page’s variants on every PostHog event. Until RoutingCat’s post part has loaded, and on a host your config doesn’t list, it passes each event as it came.

routingcat.ready()

routingcat.ready(page?: string): Promise<void>

Resolves once the latest view of the page, by its key, shows the variants RoutingCat picked, or shows its defaults, as when its routing data doesn’t come within wait_ms, the wait your config sets. Without a key, the current view’s page. It never rejects, and resolves at once for a page no view of this document has shown, so call it once the address shows the page.

routingcat.variant()

routingcat.variant(page: string, slot: string): string | null

The key of the variant a slot shows, by its page’s key and its own: the one RoutingCat picked in the page’s latest view, the slot’s default included, or in development the one ?rc= names. null before RoutingCat has picked the page’s variants, and where the page shows its defaults without a pick, as when its routing data came too late. It answers at once: call it once ready() has resolved.

From the example app, a component that waits with ready(), reads variant(), and renders the picked variant alone, in one element carrying its page, slot, variant and hash, which RoutingCat reports as what the slot showed:

src/Pricing.tsx
import { useEffect, useState } from 'react'
import declared from './slots/plan.html?raw'
const slot = document.createElement('template')
slot.innerHTML = declared
/** A variant child of the slot's markup: the one decided, else the default, not hidden. */
const variantOf = (key: string | null): Element =>
(key === null ? null : slot.content.querySelector(`[data-rc-variant="${key}"]`)) ??
(slot.content.querySelector('[data-rc-variant]:not([hidden])') as Element)
function Plan() {
const [decided, setDecided] = useState(window.routingcat === undefined)
useEffect(() => {
let mounted = true
window.routingcat?.ready('pricing').then(() => {
if (mounted) setDecided(true)
})
return () => {
mounted = false
}
}, [])
if (!decided) return null
const variant = variantOf(window.routingcat?.variant('pricing', 'plan') ?? null)
return (
<div
style={{ display: 'contents' }}
data-rc-page="pricing"
data-rc-slot="plan"
data-rc-variant={variant.getAttribute('data-rc-variant') ?? undefined}
data-rc-hash={variant.getAttribute('data-rc-hash') ?? undefined}
// biome-ignore lint/security/noDangerouslySetInnerHtml: the site's own markup, built in
dangerouslySetInnerHTML={{ __html: variant.innerHTML }}
/>
)
}
export function Pricing() {
return (
<main>
<h1>Pricing</h1>
<Plan />
</main>
)
}

routingcat.view()

routingcat.view(): void

Starts a new view of the page at the current address, as a page load does: RoutingCat picks its variants and shows them, keeping those the visitor already had on that page in the visit, and records the view outside development. In an app that renders in the browser, whose head partial init writes to take each route change as a view, RoutingCat calls it at each change of the address’s path, so a call of your own would start a second view of the same page. See Client-rendered apps.

The HTTP events API

Your server reports a conversion with one request, with the server events key, which npx routingcat keys server prints: the identity link first, as identify makes it, then the event, each with the visitor id from the request’s rc_vid cookie.

POST https://ingest.routingcat.com/v1/events
Authorization: Bearer sk_…
Content-Type: application/json
[{"type":"identity_link","id":"idl:acct_42:v_8v_ztmJDpYzDKH7UxXwXPA","visitor_id":"v_8v_ztmJDpYzDKH7UxXwXPA","account_id":"acct_42"},
{"name":"signup","id":"signup:acct_42","visitor_id":"v_8v_ztmJDpYzDKH7UxXwXPA","account_id":"acct_42"}]

The HTTP events API has every field, the limits and the answers.

The server helpers

Where your own server renders the page, as Laravel, Rails, Django, WordPress or PHP does, the server helpers set two cookies on each page’s response, which make what RoutingCat records more exact. npx routingcat init --helpers writes them in your stack’s language, adds the line that runs them, and declares them in routingcat.config.json, as "helpers": { "cookie": true, "fetch_site": true }; Build a page gives each stack’s file.

Cookie What it holds For On
rc_vid The visitor id: the one the request carries when it is valid, else a new one 400 days Your registrable domain, so that your subdomains read it
rc_fs How the visitor reached the page, typed or bookmarked, or by a link, which RoutingCat’s script reads once, then deletes 10 seconds The page’s host

They set them only on a page’s own request, never on a prefetch, with Path=/, Secure and SameSite=Lax, and rc_fs only on a GET. A page a shared cache answers runs no helper.