OwlMetry
SDKsNode.js SDK

Identity

Manage anonymous and known user identities in the Node.js SDK.

The Node.js SDK provides Owl.withUser() to associate events with a specific user. This is the primary mechanism for per-user analytics, funnel tracking, and session tracing.

withUser

Owl.withUser(userId) returns a ScopedOwl instance. Every event emitted through this instance includes the given user_id:

const owl = Owl.withUser("user_42");
owl.info("Profile updated");
owl.warn("Rate limit approaching");
owl.track("onboarding-complete");

The returned ScopedOwl has the same API as Owl -- info(), debug(), warn(), error(), track(), startOperation(), and recordMetric().

Middleware Pattern

In HTTP servers, create a scoped instance per request based on the authenticated user. This is the recommended approach for associating backend events with users.

Express

import express from "express";
import { Owl, type ScopedOwl } from "@owlmetry/node";

// Extend the Request type
declare global {
  namespace Express {
    interface Request {
      owl: typeof Owl | ScopedOwl;
    }
  }
}

const app = express();

app.use((req, res, next) => {
  req.owl = req.auth?.userId
    ? Owl.withUser(req.auth.userId)
    : Owl;
  next();
});

app.get("/api/profile", (req, res) => {
  req.owl.info("Viewed profile");
  res.json(req.user);
});

app.post("/api/settings", (req, res) => {
  req.owl.info("Updated settings", { theme: req.body.theme });
  res.json({ ok: true });
});

Fastify

import Fastify from "fastify";
import { Owl } from "@owlmetry/node";

const app = Fastify();

app.decorateRequest("owl", null);

app.addHook("onRequest", async (request) => {
  request.owl = request.user?.id
    ? Owl.withUser(request.user.id)
    : Owl;
});

app.get("/api/orders", async (request, reply) => {
  request.owl.info("Listing orders");
  const orders = await getOrders(request.user.id);
  return orders;
});

Anonymous vs Known Users

When no user ID is set (i.e., you use Owl directly instead of Owl.withUser()), events are emitted without a user_id field. They are still grouped by session_id, but cannot be attributed to a specific user.

For backend services that handle both authenticated and unauthenticated requests, the middleware pattern above handles this naturally -- unauthenticated requests fall through to the base Owl instance.

User Scoping with Operations

Operations started from a scoped instance carry the user ID across all lifecycle events (start, complete, fail, cancel):

const owl = Owl.withUser(userId);
const op = owl.startOperation("data-export");

try {
  const result = await exportUserData(userId);
  op.complete({ rows: String(result.rowCount) });
} catch (err) {
  op.fail(err.message);
}

Both the start and complete (or fail) events will have user_id set.

User Scoping with Funnels

Funnel analytics correlate steps by user ID. Always use a scoped instance when tracking funnel steps:

const owl = Owl.withUser(userId);
owl.track("signup-started");
// ... later
owl.track("email-verified");
owl.track("first-project-created");

Without a user ID, the server cannot determine which user completed which steps. See the Funnels guide for more backend funnel patterns.

Ready to get started?

Install the CLI and let your agent handle the rest.