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 neatlogsQuick 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):
| Field | Type | For |
|---|---|---|
name | string | Required. The span label (root name = workflow name). |
kind | span kind | Optional — the backend infers it from the fields present when omitted. |
input / output | unknown | The step's input and output. |
model | string | LLM model name. |
tokens | { prompt, completion, total } | Token usage. |
query / documents | unknown | Retriever / reranker query and results. |
tool_name | string | Tool / MCP tool identifier. |
passed / score | boolean / number | Guardrail result. |
duration_ms | number | Latency (simplest way — end is derived from start + duration_ms). |
metadata | object | Arbitrary metadata. |
attributes | object | Escape hatch — any canonical neatlogs.* attribute (e.g. { "neatlogs.llm.temperature": 0.7 }). Explicit attributes win over the shortcut fields. |
children | span[] | Nested spans. |
logs | { level, message, timestamp }[] | Structured logs on the span. |
Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Required. Your write key (nlw_…) or project key. |
project | string | — | Project name — required when using a write key. |
endpoint | string | https://staging-cloud.neatlogs.com | Backend base URL (self-hosted/custom only). |
enabled | boolean | true | Set false to validate calls without sending. |
onError | (err) => void | console.warn | Called 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.