Get started

Install

Install by framework, paste the project key, send the first event, then name the person.

For LLMs

You are integrating Bugwalk into an existing application. Bugwalk keeps events, errors, and logs on one timeline per person (after identify()), so an AI debug engineer can explain a failure from that visit and the connected repositories.

Ask these questions one at a time and wait for each answer unless it was already provided:

1. Project key (DSN) from Bugwalk Setup? (placeholder in docs: YOUR_PROJECT_KEY)

2. Frontend stack? Options include: Angular, Next.js, Nuxt, React, Script tag, SvelteKit, Vue

3. Backend stack, if any? Options include: Django, Echo, Express, FastAPI, Fastify, Flask, Gin, Hono, Koa, NestJS, net/http, or none

… full prompt includes every framework snippet.

Run the wizard

In the app you want to watch, run the wizard. It finds the framework, installs the package, and writes the project key from Setup.

The wizard opens a browser tab so you can approve the terminal. Approve it, go back to the terminal, and let it finish. If you would rather paste, use the snippets below. They are the same edits the wizard makes.

  1. 01

    From the app you want to watch

    npx @bugwalk/wizard
  2. 02

    Replace the project key

    Every snippet uses YOUR_PROJECT_KEY. Copy the real key from Setup and paste it in place of that string.

Frontend integrations

Install the package, drop the project key in, and wrap or register the SDK once at startup. Use the same key on the backend half of the app.

Angular

TypeScript

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$npx @bugwalk/wizard

Next.js

TypeScript

Install

$npm install @bugwalk/next

Add to app/layout.tsx

app/layout.tsx
import { BugwalkProvider } from '@bugwalk/next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <BugwalkProvider dsn="YOUR_PROJECT_KEY">{children}</BugwalkProvider>
      </body>
    </html>
  );
}

Nuxt

TypeScript

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$npx @bugwalk/wizard

React

TypeScript

Install

$npm install @bugwalk/react

Add to src/main.tsx

src/main.tsx
import { BugwalkProvider } from '@bugwalk/react';

createRoot(document.getElementById('root')!).render(
  <BugwalkProvider dsn="YOUR_PROJECT_KEY">
    <App />
  </BugwalkProvider>,
);

Script tag

HTML

Install

$<script src="https://cdn.bugwalk.dev/v1.js" data-dsn="YOUR_PROJECT_KEY" async></script>

Add to index.html

index.html
<script src="https://cdn.bugwalk.dev/v1.js" data-dsn="YOUR_PROJECT_KEY" async></script>

SvelteKit

TypeScript

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$npx @bugwalk/wizard

Vue

TypeScript

Install

$npm install @bugwalk/vue

Add to src/main.ts

src/main.ts
import { bugwalk } from '@bugwalk/vue';

createApp(App).use(bugwalk, { dsn: 'YOUR_PROJECT_KEY' }).mount('#app');

Backend integrations

Register middleware or a module before your routes. Requests from an identified browser session attach to the same person automatically.

Django

Python

Install

$pip install "bugwalk-sdk[django]"

Add to settings.py

settings.py
BUGWALK_DSN = "YOUR_PROJECT_KEY"

MIDDLEWARE = [
    "bugwalk.django.BugwalkMiddleware",
    *MIDDLEWARE,
]

Echo

Go

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$go get github.com/bugwalk-dev/bugwalk/sdks/go/echo

Express

TypeScript

Install

$npm install @bugwalk/express

Add to src/server.ts

src/server.ts
import { bugwalk } from '@bugwalk/express';

const app = express();
app.use(bugwalk({ dsn: 'YOUR_PROJECT_KEY' }));

FastAPI

Python

Install

$pip install "bugwalk-sdk[fastapi]"

Add to main.py

main.py
from bugwalk.fastapi import BugwalkMiddleware

app.add_middleware(BugwalkMiddleware, dsn="YOUR_PROJECT_KEY")

Fastify

TypeScript

Install

$npm install @bugwalk/fastify

Add to src/server.ts

src/server.ts
import { bugwalk } from '@bugwalk/fastify';

const app = fastify();
await app.register(bugwalk, { dsn: 'YOUR_PROJECT_KEY' });

Flask

Python

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$pip install "bugwalk-sdk[flask]"

Gin

Go

Install

$go get github.com/bugwalk-dev/bugwalk/sdks/go/gin

Add to main.go

main.go
import bugwalkgin "github.com/bugwalk-dev/bugwalk/sdks/go/gin"

router.Use(bugwalkgin.Middleware("YOUR_PROJECT_KEY"))

Hono

TypeScript

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$npx @bugwalk/wizard

Koa

TypeScript

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$npx @bugwalk/wizard

NestJS

TypeScript

Install

$npm install @bugwalk/nestjs

Add to src/app.module.ts

src/app.module.ts
import { BugwalkModule } from '@bugwalk/nestjs';

@Module({
  imports: [BugwalkModule.forRoot({ dsn: 'YOUR_PROJECT_KEY' })],
})
export class AppModule {}

net/http

Go

No paste snippet here yet. Run npx @bugwalk/wizard in this repo and it writes the file for you.

Install

$go get github.com/bugwalk-dev/bugwalk/sdks/go

Java, .NET, Ruby, and PHP

No Bugwalk package goes into those services. Point the OpenTelemetry agent you already run at Bugwalk and send the project key in a header.

Java, .NET, Ruby, and PHP

OpenTelemetry

Install

$OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.bugwalk.dev

Add to environment

environment
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.bugwalk.dev
OTEL_EXPORTER_OTLP_HEADERS=x-bugwalk-key=YOUR_PROJECT_KEY

How the first event arrives

You do not click a button to send the first event. The SDK sends a batch in the background after the app starts. A page view is usually enough.

Setup polls every few seconds. While it is waiting, the corner says Waiting for the first event. When a batch lands, that becomes Open the dashboard. The first page view is often there within a few seconds of the app starting.

If nothing arrives, the app is not running, the project key is a different project, or the request never left the browser. The key on the Setup page has to be the one in the snippet.

Name the person

Events record even if you never say who they belong to. They show up on Overview as sessions. They do not show up under People, and they are not a journey, until you call identify().

Call it once you know the id or email, usually right after sign-in. Events before and after that call join the same person, including requests the backend made during the visit.

People shows the email when you sent one, otherwise the id. Search accepts either, including a partial email.

Call once you know who the person is. Everything before and after joins them.

after sign-in (browser)
// Same call from React, Vue, Next.js, and the script tag SDK.
bugwalk.identify({ id: user.id, email: user.email });

When you have an internal id but no email yet.

id only
bugwalk.identify({ id: user.id });

When email is what support already has.

email only
bugwalk.identify({ email: user.email });

After the next event, People looks like this. Email is the title when you sent one.

  • sam@example.com

    u_42

    3 sessions · 2 h ago

  • u_8841

    1 session · just now

Search on People accepts sam@ or u_42. Open a row to see their journey.

Background work

A cron, a queue message, a webhook, or a script answers two questions, and either one can be left out. What is the work? Pass a stable id and a kind: cron, queue, or task. Who is it for? Pass the same id or email you would pass to identify().

withWork names the run and starts a new session. forPerson attributes one iteration to a person, then clears that person so the next iteration does not inherit them. Call forPerson inside withWork when the job runs for many people. Call forPerson alone when the work is for one person and has no job name.

A run with no person shows on Overview as a session. It does not show under People. A run that calls forPerson shows each person under People, the same way a signed-in visit does. An error is still an issue. It names the person when one was set, and it keeps the job when the run was named.

Leave identify() for web requests. Calling it with an id such as cron:daily-digest files that string under People.

Nobody owns this run. It shows on Overview as a session. It does not show under People.

unscoped cron
import { withWork } from '@bugwalk/node';

await withWork({ id: 'temp-cleanup', kind: 'cron' }, async () => {
  await deleteOldFiles();
});

The job is the whole run. forPerson uses the same id and email as identify(), on that person’s own session, then clears them.

cron for each person
import { forPerson, withWork } from '@bugwalk/node';

await withWork({ id: 'daily-digest', kind: 'cron' }, async () => {
  for (const person of people) {
    await forPerson({ id: person.id, email: person.email }, async () => {
      await sendDigest(person);
    });
  }
});

kind is queue. The person is still forPerson, not a fake identify() id.

queue message for one person
await withWork({ id: 'process-order', kind: 'queue' }, async () => {
  await forPerson({ id: message.personId }, async () => {
    await fulfill(message);
  });
});

An invoice, a webhook, or a retry. forPerson alone puts them under People. Add withWork with kind task when you also want a name on the run.

work for a person, not a cron
await forPerson({ id: person.id, email: person.email }, async () => {
  await sendInvoice(person);
});

await withWork({ id: 'send-invoice', kind: 'task' }, async () => {
  await forPerson({ id: person.id }, () => sendInvoice(person));
});

@Cron sits above @BugwalkJob so the scheduler calls the wrapped method. The decorator does not walk the list. The loop calls forPerson.

Nest cron
import { BugwalkJob, forPerson } from '@bugwalk/nestjs';

@Cron('0 2 * * *')
@BugwalkJob({ id: 'daily-digest', kind: 'cron' })
async sendDigests() {
  for (const person of await this.people.due()) {
    await forPerson({ id: person.id, email: person.email }, () => this.mailer.send(person));
  }
}

with_work and for_person. The person does not stick to the process.

Python
def send(person):
    bugwalk_sdk.capture("digest_sent")

def run(_run):
    for person in people:
        bugwalk_sdk.for_person(
            lambda person=person: send(person),
            id=person["id"],
            email=person["email"],
        )

bugwalk_sdk.with_work(run, id="daily-digest", kind="cron")

Pass the context into Capture. Identify on the client would leak across goroutines.

Go
err := bugwalk.WithWork(ctx, bugwalk.Work{ID: "daily-digest", Kind: "cron"}, func(ctx context.Context, _ bugwalk.Run) error {
    for _, person := range people {
        if err := bugwalk.ForPerson(ctx, bugwalk.Person{ID: person.ID, Email: person.Email}, func(ctx context.Context) error {
            return sendDigest(ctx, person)
        }); err != nil {
            return err
        }
    }
    return nil
})

Java, .NET, Ruby, and PHP set bugwalk.job.id on the span. bugwalk.job.runner is cron, queue, or task. Person attributes stay as they are. One span per person is the same record as one forPerson call.