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()
routingcat.link(url: string | URL): stringThe 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 | nullThe 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:
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(): voidStarts 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/eventsAuthorization: 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.