---
title: "Client"
description: "Send browser logs to the console, queue and retry remote delivery, and parse failed fetch responses safely."
canonical_url: "https://www.blyp.dev/docs/integrations/client"
markdown_url: "https://www.blyp.dev/docs/integrations/client.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
---

# Client
URL: /docs/integrations/client
LLM index: /llms.txt
Description: Send browser logs to the console, queue and retry remote delivery, and parse failed fetch responses safely.

# Client

The `@blyp/core/client` entry gives you two major capabilities:

- browser logging with optional remote sync
- browser-safe error parsing with `parseError()`
- connector requests for PostHog, Sentry, or named OTLP targets through Blyp ingestion

For Expo apps, use the dedicated `@blyp/core/expo` logger instead of `@blyp/core/client`.

## `createClientLogger()`

```ts
import { createClientLogger } from "@blyp/core/client";

const logger = createClientLogger({
  endpoint: "/inngest",
  credentials: "same-origin",
  metadata: () => ({
    app: "dashboard",
    release: "2026.03.10",
  }),
  traceId: "trace_123",
  delivery: {
    maxRetries: 3,
    retryDelayMs: 5000,
  },
});

logger.info("hydrated", { route: window.location.pathname });
logger.error(new Error("button failed to render"));
logger.child({ feature: "checkout" }).warn("client validation failed");
```

## When to use it

Use the browser client when you want Blyp to:

- mirror logs to the browser console during development
- forward browser events to your backend ingestion endpoint
- buffer transient failures and retry when connectivity returns
- turn failed `fetch()` responses into structured `BlypError` instances

If your app only needs local browser logging and no remote sync, disable `remoteSync`.

```ts
const logger = createClientLogger({
  endpoint: "/inngest",
  remoteSync: false,
});
```

## Defaults

| Option | Default |
| --- | --- |
| `endpoint` | `/inngest` |
| `credentials` | `"same-origin"` |
| `localConsole` | `true` |
| `remoteSync` | `true` |
| `traceId` | not set |
| `delivery.maxRetries` | `3` |
| `delivery.retryDelayMs` | `5000` |
| `delivery.maxQueueSize` | `100` |

## Delivery behavior

Remote browser delivery is managed by a shared in-memory dispatcher:

- failed events are queued in memory
- retryable failures are retried up to `3` times by default
- the default retry delay is `5000ms`
- the queue keeps at most `100` pending events
- when the queue is full, Blyp drops the oldest queued item
- child loggers reuse the same dispatcher instead of creating separate queues
- when the browser comes back online, Blyp resumes queued delivery automatically

Retryable failures are:

- offline browser state
- network errors
- HTTP `429`
- HTTP `5xx`

Non-retryable response failures, such as `400`, fail immediately and trigger terminal failure handling.

If you do not set custom headers, the client logger still uses `navigator.sendBeacon()` as a last-chance fallback when `fetch(..., { keepalive: true })` fails.

## Delivery hooks

Use `delivery` to customize retry policy and observe outcomes:

```ts
const logger = createClientLogger({
  delivery: {
    maxRetries: 3,
    retryDelayMs: 5000,
    maxQueueSize: 100,
    warnOnFailure: true,
    onRetry: (ctx) => {
      console.log("retrying", ctx.attempt, ctx.retriesRemaining);
    },
    onSuccess: (ctx) => {
      console.log("delivered via", ctx.transport);
    },
    onFailure: (ctx) => {
      console.error("terminal delivery failure", ctx.reason, ctx.status);
    },
    onDrop: (ctx) => {
      console.warn("dropped", ctx.droppedEvent.message, "for", ctx.replacementEvent.message);
    },
  },
});
```

### Hook context highlights

- `onRetry`: receives `attempt`, `retriesRemaining`, `nextRetryAt`, `reason`, `status`, and `error`
- `onSuccess`: receives `attempt`, `status`, and `transport` (`"fetch"` or `"beacon"`)
- `onFailure`: receives the terminal `reason`, plus `status` or `error` when available
- `onDrop`: fires when queue overflow forces Blyp to evict the oldest queued event

## Event shape

Client events are normalized into a `ClientLogEvent` shape:

```ts
type ClientLogEvent = {
  type: "client_log";
  source: "client";
  id: string;
  level: "debug" | "info" | "warning" | "error" | "critical" | "success" | "table";
  message: string;
  data?: unknown;
  bindings?: Record<string, unknown>;
  clientTimestamp: string;
  page: ClientLogPageContext;
  browser: ClientLogBrowserContext;
  device?: ClientLogDeviceContext;
  session: { pageId: string; sessionId: string };
  metadata?: Record<string, unknown>;
  traceId?: string;
};
```

## Browser error parsing

```ts
import { parseError } from "@blyp/core/client";

const response = await fetch("/api/payments", { method: "POST" });

if (!response.ok) {
  throw await parseError(response);
}
```

See [Parse Error](/docs/errors/parse-error) for server and browser parsing patterns in more detail.

## Pairing with server adapters

Most server-side adapters automatically register or expose a handler for the configured client ingestion path:

- programmatic server adapters auto-register `POST /inngest`
- file-based integrations expose a handler helper you mount yourself
- ingested events are tagged as `type: "client_log"` and `source: "client"`

See the individual pages under [Integrations](/docs/integrations) for exact mounting patterns.

## Joining a server trace

If your frontend wants its browser logs to join the same trace as the server request that rendered or handled it, pass the server response header through to the client logger:

```ts
const response = await fetch("/api/session");
const traceId = response.headers.get("x-blyp-trace-id") ?? undefined;

const logger = createClientLogger({
  endpoint: "/inngest",
  traceId,
});

logger.info("session hydrated");
```

This is optional for standalone browser logs. Use it when you want browser-originated logs, connector-forwarded logs, and database records to share the same correlation ID.

## Connector forwarding requests

The browser client can ask Blyp ingestion to forward logs into a configured connector:

```ts
const posthogLogger = createClientLogger({
  endpoint: "/inngest",
  connector: "posthog",
});

const sentryLogger = createClientLogger({
  endpoint: "/inngest",
  connector: "sentry",
});

const otlpLogger = createClientLogger({
  endpoint: "/inngest",
  connector: { type: "otlp", name: "grafana" },
});
```

Important behavior:

- the browser still posts to Blyp first
- Blyp forwards only when the matching server connector is configured
- Blyp warns once if the requested connector is missing on the server
- PostHog client `error` and `critical` logs are promoted into Error Tracking only when server-side PostHog error tracking runs in `auto` mode

Use [Connectors](/docs/connectors) for the server-side setup behind these requests.

## Expo mobile logging

Expo has its own logger because it does not run in a browser environment and cannot rely on same-origin delivery.

```ts
import { createExpoLogger } from "@blyp/core/expo";

const logger = createExpoLogger({
  endpoint: "https://api.example.com/inngest",
  metadata: () => ({
    app: "mobile",
  }),
  delivery: {
    maxRetries: 3,
    retryDelayMs: 5000,
  },
});
```

Use [Expo](/docs/integrations/expo) for the full mobile setup and its delivery constraints.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
