Learn
Contributing

Contributing

This guide covers how to contribute to Traceway, from filing an issue to getting a PR merged.

Community

The fastest way to ask questions, share ideas, or get help from maintainers is Discord (opens in a new tab). Issues and PRs are the right place for concrete bugs and changes; Discord is for everything else.

Opening Issues

Bug reports

Search open issues first to avoid duplicates. When you file a bug, include:

  • What happened: a clear description of the bug
  • What you expected: what should have happened instead
  • Steps to reproduce: the smallest sequence of steps that triggers it
  • Environment: Traceway version, deployment type (SQLite, Docker Compose, all-in-one), OS
  • Logs: any relevant error messages or stack traces

The more precise the reproduction steps, the faster the fix.

Feature requests

Open an issue before writing code. Describe the problem you're solving and what you have in mind. This avoids building something that conflicts with existing plans, duplicates in-progress work, or doesn't fit the project's direction. A quick conversation upfront saves everyone time.

Labels

Maintainers add labels after the issue is filed. You don't need to set them yourself.

Project Structure

DirectoryDescription
backend/Go/Gin API server: handles telemetry ingestion, REST API, and database access
frontend/SvelteKit 2 dashboard: the web UI users interact with
docs/Nextra documentation site (what you're reading now)
examples/Working example apps demonstrating embedded mode
scripts/Helper scripts for testing and CI

Tech Stack

ComponentTechnology
BackendGo 1.26, Gin, ClickHouse, PostgreSQL
FrontendSvelteKit 2, Svelte 5, Tailwind CSS v4, shadcn-svelte
Embedded modeSQLite (default build, no external dependencies)
DocsNextra (Next.js)

Prerequisites

  • Go 1.26 or later
  • Node.js 22 and npm (frontend/package.json pins engines.node and frontend/.npmrc enforces it)
  • Nix (optional): nix develop at the repository root opens a shell with the pinned Go and Node plus just, golangci-lint and govulncheck. .envrc wires it into direnv, and .envrc.local (gitignored) is the place for JWT_SECRET.
  • Docker (for running the full ClickHouse + PostgreSQL test suite)
  • ClickHouse and PostgreSQL (only if running the standalone server locally; not needed for embedded/SQLite mode)

Build Tags

Traceway uses Go build tags to switch between storage backends:

TagPurpose
(none)SQLite: embedded mode, zero dependencies. This is the default.
telemetry_duckdbDuckDB telemetry: embedded mode with a columnar telemetry store. Requires CGO_ENABLED=1.
transactional_pg telemetry_chClickHouse + PostgreSQL: standalone server mode.
localdistEmbeds the pre-built frontend into the Go binary. Used in Docker builds.

See the Build Tags page for more details.

Setting Up for Development

Backend (embedded/SQLite mode)

The fastest way to get going (no databases to install):

cd backend
go build ./...
go test ./...

This builds and tests the SQLite backend, which is the default.

Backend (standalone server mode)

If you're working on ClickHouse or PostgreSQL-specific code, see the Local Setup guide for database installation and environment variables. Then:

cd backend
go build -tags "transactional_pg telemetry_ch" ./...
go run -tags "transactional_pg telemetry_ch" ./cmd/traceway

Frontend

cd frontend
npm install
npm run dev

The dev server runs on port 5173 and proxies API requests to the backend on port 8082.

Docs

cd docs
npm install
npm run dev

Running Tests

SQLite tests (default)

These run without any external dependencies:

cd backend
go test -v -count=1 ./app/repositories/...

ClickHouse + PostgreSQL tests (via Docker)

The full ClickHouse + PostgreSQL test suite runs both databases inside a Docker container. This is the most reliable way to verify ClickHouse-specific code:

./scripts/test-backend-pgch.sh

This builds a Docker image with both databases, applies all migrations, and runs the test suite with -tags "transactional_pg telemetry_ch". All tests should pass.

Without Docker, you can run the ClickHouse + PostgreSQL tests against local databases by setting environment variables:

cd backend
TEST_CLICKHOUSE_SERVER=localhost:9000 \
TEST_CLICKHOUSE_DATABASE=traceway_test \
TEST_POSTGRES_HOST=localhost \
TEST_POSTGRES_DATABASE=traceway_test \
TEST_POSTGRES_USERNAME=traceway \
TEST_POSTGRES_PASSWORD=traceway \
go test -tags "transactional_pg telemetry_ch" -v -count=1 ./app/repositories/...

Without TEST_CLICKHOUSE_SERVER set, the ClickHouse tests skip cleanly.

OTEL trace converter tests

These test the OTLP trace parsing logic using snapshot/golden-file testing. No database required: convertTraces() is a pure function.

cd backend
go test -v -count=1 ./app/controllers/otelcontrollers/

Real OTEL JSON payloads are stored in testdata/ as fixtures. The tests parse them through the converter and compare the output against .golden.json files. If you intentionally change the converter, regenerate the golden files:

cd backend
go test -v -count=1 -args -update ./app/controllers/otelcontrollers/

Then review the diff in the .golden.json files to confirm the changes are expected before committing.

Frontend checks

cd frontend
npm run check    # TypeScript checking
npm run build    # Production build

Architecture Overview

Data Flow

Application → [OpenTelemetry] → POST /api/otel/v1/* → Backend → ClickHouse/SQLite/DuckDB
                                            ↓
Dashboard ← [SvelteKit Frontend] ← JSON API ← Gin Controllers

The SDK sends telemetry (traces, exceptions, metrics) as gzipped JSON to the backend. The backend stores it in ClickHouse (standalone) or SQLite (embedded), and the frontend reads it via REST API.

Authentication

Two separate auth systems:

  • Client auth: Project bearer tokens, used by the SDK to send telemetry (Authorization: Bearer <project_token>)
  • App auth: JWT tokens, used by the dashboard for user sessions (Authorization: Bearer <jwt_token>)

Database Split

  • PostgreSQL (or SQLite in embedded mode): Relational data that gets updated, such as users, organizations, projects, invitations, notification rules, dashboards and templates
  • ClickHouse (or SQLite in embedded mode): High-volume append-only telemetry, such as traces, exceptions, metrics, spans, tasks, session recordings

Backend Patterns

Repositories are singletons (var FooRepository = &fooRepository{}) organized on the two storage axes. Telemetry repositories have one implementation per backend under backend/app/repositories/telemetry/{clickhouse,sqlite,duckdb}/, selected by the telemetry_* build tags; transactional (relational) repositories live under backend/app/repositories/transactional/{pg,sqlite}/, selected by the transactional_* build tags. Each axis is re-exported through its facade package; callers import app/repositories/telemetry or app/repositories/transactional (e.g. telemetry.SpanRepository, transactional.UserRepository) and never a backend package directly.

Controllers use the Gin framework. PostgreSQL operations go through middleware.Transactional for automatic commit/rollback.

Migrations live in backend/app/migrations/ch/ (ClickHouse) and backend/app/migrations/pg/ (PostgreSQL). Each ClickHouse migration file must contain exactly one SQL statement. Migrations run automatically on startup.

Frontend Patterns

State is managed with Svelte 5 runes ($state, $derived, $effect) in singleton classes exported from src/lib/state/.

API calls go through src/lib/api.ts which auto-attaches auth tokens and the current project ID.

Components use shadcn-svelte (in src/lib/components/ui/) with custom Traceway wrappers (in src/lib/components/traceway/).

Forking and Opening PRs

Fork and clone

  1. Fork the repository on GitHub
  2. Clone your fork:
git clone https://github.com/YOUR_USERNAME/traceway.git
cd traceway
  1. Add the upstream remote:
git remote add upstream https://github.com/tracewayapp/traceway.git

Create a branch

Always branch from main:

git fetch upstream
git checkout -b your-feature upstream/main

Before submitting

  1. Build all backends to make sure nothing is broken:
cd backend
go build ./...                                    # SQLite (default)
go build -tags "transactional_pg telemetry_ch" ./...       # ClickHouse + PostgreSQL
CGO_ENABLED=1 go build -tags telemetry_duckdb ./...   # DuckDB telemetry
  1. Run the SQLite tests:
go test -v -count=1 ./app/repositories/...
  1. Run the OTEL converter tests if you changed any OTEL parsing code:
go test -v -count=1 ./app/controllers/otelcontrollers/
  1. Run the ClickHouse + PostgreSQL Docker tests if you changed any repository code:
./scripts/test-backend-pgch.sh
  1. Check the frontend if you changed any frontend code:
cd frontend
npm run check
npm run build

Open a pull request

Push your branch and open a PR against main:

git push origin your-feature

Then create a pull request on GitHub. Include:

  • A short description of what the change does and why
  • Steps to test, if applicable
  • Screenshots for UI changes

The backend and CLI workflows do not start on their own when a PR is opened. A maintainer applies the ci label to run them against the PR as it stands, and re-applies it after new pushes. That is why the local checks above matter: they are the only feedback until the label goes on.

Keeping your fork up to date

git fetch upstream
git rebase upstream/main

PR Review Process

All pull requests require at least one approving review before merging. This applies to everyone, including maintainers.

What reviewers look at

  • Correctness: does the code do what the PR says it does?
  • Testing: are the requirements in "Before submitting" met? Repository changes need the ClickHouse + PostgreSQL Docker tests to pass.
  • Patterns: does the change follow the conventions in CLAUDE.md (opens in a new tab)? Reviewers will push back on divergence from established patterns.
  • Scope: is the PR focused? A small, focused PR is always preferred over one that mixes unrelated changes.

Merge strategy

PRs are squash-merged, so write a clear PR title and description; they become the commit message on main. Don't just say "fix bug"; say what bug, why it happened, and what you changed.

Review timeline

Maintainers aim to review PRs within a few days. If a PR has been open for more than a week with no response, ping in Discord. If you push new commits after requesting review, leave a comment explaining what changed. Don't silently update the branch.

Using AI Tools

AI is fine. It is a tool, no different than IntelliJ or VSCode autocomplete, and it will be treated as such. Use whatever AI you find good to help write your code. What does not change is ownership of the result.

Ground rules:

  • You are personally responsible for your code. Whether you typed it, an autocomplete suggested it, or an AI agent generated the whole file, it is your code the moment it lands in a PR.
  • Read and understand every line. You are expected to understand each line you submit. Read the diff. If you cannot explain why a line is there or what it does, it is not ready for a PR. Reviewers will ask.
  • Follow the project's guidelines and keep the style consistent, regardless of how the code was written. AI does not know the conventions in this repo; you do. Cross-check generated code against CLAUDE.md (opens in a new tab), especially the error handling patterns, transaction middleware, and repository patterns.
  • Test locally, rigorously, before committing. Do not rely on AI saying "this should work." A human runs the code and the relevant tests (see "Before submitting") and confirms it works before it is committed.

There is no stigma around AI usage here. The bar is the same as it has always been: correct, tested, idiomatic code that you understand and stand behind.