Backends

Express and Node.js
Express analytics with Stickybeak starts by adding its script to your rendered pages; this guide shows you where it belongs and how to track page views, goals and purchase revenue.
If your HTML comes from Next.js, Remix or another React framework rather than Express views, follow Next.js or React instead.
Add the script
Set the site ID
Set
STICKYBEAK_SITE_IDin your production environment only.// Only set in production, so dev and staging aren't counted. app.locals.stickybeakSite = process.env.STICKYBEAK_SITE_ID; // JSON that's safe inside a <script> tag. app.locals.toScript = (value) => JSON.stringify(value ?? null).replace(/</g, "\\u003c");Add it to your layout
EJS has no layouts of its own, so most apps include a shared head partial. Put this before
</head>:<script>window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };</script> <% if (stickybeakSite) { %> <script defer data-site="<%= stickybeakSite %>" data-domain="example.com" src="https://app.usestickybeak.com/_cfw/s.js" ></script> <% } %>
Pug
doctype html
html
head
script.
window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };
if stickybeakSite
script(defer data-site=stickybeakSite data-domain="example.com" src="https://app.usestickybeak.com/_cfw/s.js")
body
block content
Handlebars
With express-handlebars, the default layout is views/layouts/main.handlebars:
<head>
<script>window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };</script>
{{#if stickybeakSite}}
<script
defer
data-site="{{stickybeakSite}}"
data-domain="example.com"
src="https://app.usestickybeak.com/_cfw/s.js"
></script>
{{/if}}
</head>
Fastify, Koa, NestJS and others
The templates are the same whichever Node server renders them.
Check it's working
Publish first, then follow Verify your install.
Track goals
Give any button or link a data-goal attribute. Extra data-goal-* attributes become properties. See Custom goals.
<a href="/pricing" data-goal="view_pricing" data-goal-from="hero">See pricing</a>
Fire a goal after a redirect
For a signup or a paid order, the route that saves it doesn't render a page; it redirects.
// After app.use(session(...)): move a pending goal from the session into the view.
app.use((req, res, next) => {
res.locals.stickybeakGoal = req.session.stickybeakGoal;
delete req.session.stickybeakGoal;
next();
});
app.post("/signup", async (req, res, next) => {
const user = await createUser(req.body);
req.session.stickybeakGoal = { name: "signup", props: { plan: user.plan } };
req.session.save((error) => {
if (error) return next(error);
res.redirect("/welcome");
});
});
Saving before the redirect prevents the next request from reaching the session store before the goal has been written.
<% if (stickybeakGoal) { %>
<script>window.stickybeak(<%- toScript(stickybeakGoal.name) %>, <%- toScript(stickybeakGoal.props || {}) %>);</script>
<% } %>
<%- outputs without HTML escaping, which is right here because toScript has already made the value safe for a script.
Track purchases
req.session.stickybeakGoal = {
name: "purchase",
props: { revenue: order.total, currency: order.currency, order_id: String(order.id) },
};
req.session.save((error) => {
if (error) return next(error);
res.redirect(`/orders/${order.id}/thanks`);
});
See Revenue.
If it isn't working
- Confirm
STICKYBEAK_SITE_IDreaches the Node process. Server-rendered React should use its framework guide instead.
See Troubleshooting.