Browser SDK

Send traces to Neatlogs directly from front-end web apps with the zero-dependency browser client.

The browser SDK (neatlogs/browser) is a tiny, zero-dependency client for sending traces straight from a web app. It has only fetch, so it bundles cleanly into front-end code. It POSTs plain JSON to the backend, which generates the trace and span IDs, builds the hierarchy from your nesting and infers cost from model + tokens.

Use a write key, not your project key. Browser code is public. Create a write key (nlw_…) — an ingest-only credential safe to embed client-side — and pass your project name as project.

Install

npm install neatlogs

Quick start

import { Neatlogs } from 'neatlogs/browser';

const nl = new Neatlogs({
  apiKey: 'nlw_your_write_key',   // ingest-only write key
  project: 'my-web-app',          // your project NAME (required with a write key)
});

// One AI interaction → a one-span LLM trace
await nl.trackAI({
  name: 'chat',
  model: 'gpt-4o',
  input: 'What is the capital of France?',
  output: 'Paris.',
  tokens: { prompt: 12, completion: 3 },
});

That's the whole setup — the call renders as a trace in your dashboard. Telemetry never throws into your app: transport errors go to onError (default console.warn).

Sending a nested trace

Pass a tree of spans via children; the backend wires up the parent/child hierarchy. The root node's name becomes the workflow name.

await nl.trace({
  name: 'support-chat',
  children: [
    { name: 'retrieve', kind: 'RETRIEVER', query: userQuestion, documents: docs },
    { name: 'answer', model: 'gpt-4o', input: prompt, output: reply,
      tokens: { prompt: 240, completion: 60 } },
  ],
});

Streaming: start then finish

When the output arrives incrementally, open the trace up front and complete it when done — nothing is sent until finish():

const t = nl.startTrace({ name: 'chat', model: 'gpt-4o', input: prompt });
// … stream tokens …
await t.finish({ output: fullText, tokens: { prompt: 20, completion: 130 } });

Span fields

Each span node accepts these shortcut fields (all optional except name):

FieldTypeFor
namestringRequired. The span label (root name = workflow name).
kindspan kindOptional — the backend infers it from the fields present when omitted.
input / outputunknownThe step's input and output.
modelstringLLM model name.
tokens{ prompt, completion, total }Token usage.
query / documentsunknownRetriever / reranker query and results.
tool_namestringTool / MCP tool identifier.
passed / scoreboolean / numberGuardrail result.
duration_msnumberLatency (simplest way — end is derived from start + duration_ms).
metadataobjectArbitrary metadata.
attributesobjectEscape hatch — any canonical neatlogs.* attribute (e.g. { "neatlogs.llm.temperature": 0.7 }). Explicit attributes win over the shortcut fields.
childrenspan[]Nested spans.
logs{ level, message, timestamp }[]Structured logs on the span.

Constructor options

OptionTypeDefaultDescription
apiKeystringRequired. Your write key (nlw_…) or project key.
projectstringProject name — required when using a write key.
endpointstringhttps://staging-cloud.neatlogs.comBackend base URL (self-hosted/custom only).
enabledbooleantrueSet false to validate calls without sending.
onError(err) => voidconsole.warnCalled on transport errors instead of throwing.

Under the hood the browser SDK calls the same POST /v1/trace endpoint documented in the HTTP API — use that directly from any language or runtime that isn't JavaScript.

On this page