HivePaaS
Docker Swarm 기반 오픈소스 자체 호스팅 PaaS, SSL·백업·자동 확장 지원
Pinwren은 자체 호스팅 시각 피드백 도구로, 한 스크립트 태그로 요소에 댓글을 달고 오류와 실패 요청을 포함한 버그 보고서를 전송합니다
Pinwren is a self-hostable visual feedback tool. One script tag lets clients pin comments on any element and send annotated bug reports with console errors and failed requests; your team triages them
개발 도구TypeScript · MDXGitHub @Krithik-m-codes온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Pinned feedback and annotated bug reports for any website — self-hosted.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Pinned feedback and annotated bug reports for any website — self-hosted.
One script tag adds a widget where clients and teammates pin comments on exact page elements, Figma-style, or send an annotated screenshot with console errors, failed requests and browser details. Everything lands in a keyboard-first triage inbox you run yourself.
Website · Quickstart · Architecture · Contributing · Security
Status: v0.2 — early. The core loop (pin, report, triage, sync to your tracker) works and is covered by unit, Playwright and end-to-end API tests. Expect rough edges and breaking changes before 1.0. The full product spec and roadmap live in
blueprint/.
On your website (the widget)
?pinwren=<id>) that open the page with the thread showing, and re-pinning a moved pin.data-pinwren-mask, are
always masked.In the dashboard
j/k, e, s, p, a, l, x, u, /, g m)./api/v1, and custom panel actions
such as "Send to agent".| Pinwren | Marker.io | BugHerd | Userback | BugPin | SitePing | |
|---|---|---|---|---|---|---|
| Open source | ✅ AGPL-3.0, widget MIT | — | — | — | AGPL-3.0 server/admin; MIT widget | ✅ MIT |
| Self-hosted | ✅ Docker + Postgres | — | — | — | ✅ | ✅ inside your own app |
| Comments pinned to page elements | ✅ | — | ✅ | Partial | — | ✅ |
| Annotated screenshot bug reports | ✅ | ✅ | ✅ | ✅ | ✅ | Partial (area capture) |
| Console and network capture | ✅ | ✅ | Partial | ✅ | ✅ | ✅ |
| Session replay | ✅ new in 0.1 | ✅ | — | ✅ | — | — |
| Triage dashboard | ✅ built in | ✅ | ✅ | ✅ | ✅ | ✅ React component |
| GitHub / GitLab issue sync | ✅ two-way | ✅ | ✅ | ✅ | GitHub, two-way status sync | ✅ |
| Jira, Linear and other trackers | Linear; Jira (Enterprise) | ✅ | ✅ | ✅ | Custom API/webhooks (Enterprise) | — |
| Price | Free to self-host | Paid SaaS | Paid SaaS | Paid SaaS | Community free; Enterprise paid | Free |
Competitor columns reflect their public documentation as of October 2026 and change often; if a cell is wrong, please open an issue. "Partial" means the feature exists with notable limits or only on some plans.
You need Docker with Compose. The stack is the Pinwren container plus Postgres 17.
git clone https://github.com/Krithik-m-codes/pinwren.git && cd pinwren
cp docker/.env.example docker/.env # set PINWREN_URL, PINWREN_SECRET, POSTGRES_PASSWORD
docker compose -f docker/compose.yml --env-file docker/.env up -d --build
Each release also publishes a multi-arch image (amd64, arm64) to ghcr.io/krithik-m-codes/pinwren: set
PINWREN_IMAGE=ghcr.io/krithik-m-codes/pinwren:<version> in docker/.env and leave out --build.
Then, all in the browser:
PINWREN_URL and sign up. The first account on a new server is its
administrator — there is no setup token to dig out of the logs. Keep the server private until you have
done this (it listens on 127.0.0.1 by default).SMTP_*, EMAIL_FROM in
docker/.env) still work and lock the fields they set.The full walkthrough is in docs/self-hosting/getting-started.md.
To put it on a VPS with a domain and HTTPS (Docker Compose or a Portainer stack, nginx + Let's Encrypt, backups,
upgrades), follow docs/self-hosting/deploy.md; to host this project's website and docs,
docs/self-hosting/website.md.
In production, put a TLS reverse proxy (Caddy, Traefik, nginx) in front: allow request bodies of at least
48 MB (screenshots, attachments and a 10 MB session replay) and let it compress the widget with brotli —
ready-made configs are in docs/self-hosting/reverse-proxy.md. Data lives in two volumes: pinwren-db (Postgres) and pinwren-data
(uploaded files). Every variable in docker/.env reaches the app; each is described in
docker/.env.example, apps/web/.env.example and
blueprint/08-self-hosting.md.
Paste the snippet from the project's Install tab before </body> (the tab also has Next.js, Google Tag
Manager, npm and React versions, prefilled with your key and server address, and a npx @pinwren/cli doctor
command that checks the setup from your terminal):
<script src="https://feedback.example.com/widget.js" data-project="pk_…" async></script>
The widget only talks to your Pinwren server, identifies itself with the project's public key, and is accepted only from the origins you allowed — no secret ever reaches the browser. To bundle it with your app instead, install it from npm and start it on the client:
npm install @pinwren/widget
import { identify, init, on } from "@pinwren/widget"; // a tiny loader: the widget comes from your server
init({ projectKey: "pk_…", endpoint: "https://feedback.example.com" });
identify({ name: "Ada Lovelace", email: "[email protected]" });
on("submit", ({ number }) => console.log(`Feedback #${number} sent`));
In React, npm install @pinwren/react and render <Pinwren projectKey="pk_…" endpoint="https://feedback.example.com" />
once near the root of your app.
your website your server (one container) storage
┌──────────────────┐ /w/* (public ┌──────────────────────────────────────────┐ ┌──────────────┐
│ widget.js │ key + origin) │ Next.js 16 dashboard (App Router) │ │ Postgres │
│ closed Shadow │ ────────────────▶ │ └─ Hono API mounted at /api and /w │──▶│ data, auth, │
│ DOM, lazy │ │ Better Auth sessions, rate limits │ │ job queue │
│ chunks │ ◀─ chunks, i18n ─ │ pg-boss jobs: email, webhooks, issue │ └──────────────┘
└──────────────────┘ │ sync, retention, update check │ ┌──────────────┐
│ node:cluster workers │──▶│ disk or S3 │
dashboard (browser) ──── /api ──────▶ └──────────────────────────────────────────┘ └──────────────┘
packages/core, shared by the API, the dashboard and the widget./api through React Query, compiled with the React
Compiler, with Cache Components and typed routes.Decisions are recorded in docs/adr/ and the
blueprint's decision log.
| Path | Package | Licence | What it is |
|---|---|---|---|
apps/web | @pinwren/web | AGPL-3.0 | Next.js 16 dashboard; mounts the API at /api and /w |
apps/www | @pinwren/www | AGPL-3.0 | The website: landing page, docs (Fumadocs) and the live demo with an in-memory API |
packages/api | @pinwren/api | AGPL-3.0 | Hono API: auth, dashboard and widget routes, jobs, email, webhooks, trackers, storage |
packages/core | @pinwren/core | AGPL-3.0 | Zod schemas and wire types, origin matching, security helpers |
packages/db | @pinwren/db | AGPL-3.0 | Drizzle schema and SQL migrations (Postgres) |
packages/widget | @pinwren/widget | MIT | The embeddable widget, on npm: widget.js (≈33 KB gzip) plus lazily loaded chunks |
docker | — | AGPL-3.0 | Dockerfile and the self-host compose file (development uses the root compose.yaml) |
loadtest | — | AGPL-3.0 | k6 scenario and sizing notes (results) |
brand | — | — | Logo (SVG, PNG, favicons), palette and usage rules (brand/README.md) |
You need Node 22.12+, pnpm 12 (corepack enable) and Docker for the development database.
Just want to click around? docker compose --profile app up --build (or pnpm app:up) builds this checkout and
serves it on http://localhost:3000 with its own database — no Node needed. Sign up: the first account is the admin.
pnpm install
pnpm db:up # docker compose up -d: Postgres on 127.0.0.1:5433
cp apps/web/.env.example apps/web/.env.local # then set PINWREN_SECRET
pnpm --filter @pinwren/widget build # widget.js into apps/web/public
pnpm dev # dashboard :3100, website :3200, widget rebuilds on save
| Command | What it does |
|---|---|
pnpm verify | Format check, lint, typecheck, unit tests and build — run before a PR |
pnpm format / pnpm format:check | Prettier, with Tailwind class sorting |
pnpm lint / pnpm lint:fix | ESLint: typescript-eslint strict, Next.js, React Compiler, a11y |
pnpm typecheck | tsc in every package (strict, exactOptionalPropertyTypes) |
pnpm test | Vitest unit tests (core, api, widget) |
pnpm test:e2e | Playwright: the widget (port 5180) and the dashboard (port 3100) |
node scripts/smoke.mjs http://localhost:3100 | End-to-end API checks against a running server |
Try the widget on the demo page: allow localhost:5173 in a project, run node scripts/demo-server.mjs,
then open http://localhost:5173/?key=<project key>&host=http://localhost:3100.
apps/www)The public site — landing page, documentation and the live demo — is its own Next.js app. It needs no
database: the demo talks to an in-memory API in the app's route handlers, and the docs live as MDX in
apps/www/content/docs. Before every dev, build and typecheck, scripts/prepare.mjs generates the environment
reference from packages/api/src/env.ts, the changelog page from CHANGELOG.md, and copies the built widget
from packages/widget/dist for the demo.
pnpm --filter @pinwren/widget build # once, for the demo
pnpm --filter @pinwren/www dev # http://localhost:3200
pnpm --filter @pinwren/www test:e2e # Playwright on :3200 (builds and starts the site if it isn't running)
docker build -f apps/www/Dockerfile -t pinwren-www . && docker run --rm -p 3200:3000 pinwren-www
Hosting the demo publicly: it's anonymous and in memory, so it is capped — 2,000 sandboxes, 6 MiB each, 192 MiB
in total (DEMO_MAX_*), with per-visitor and per-address write limits. Request headers are not trusted by
default, so every visitor shares one rate-limit bucket. Set DEMO_TRUST_PLATFORM_IP_HEADER when the platform
sets a client-IP header it controls (e.g. x-vercel-forwarded-for on Vercel), or PINWREN_TRUST_PROXY to the
number of proxies in front of the site (X-Forwarded-For is then read from the right).
Issues and pull requests are welcome — please read CONTRIBUTING.md first. Commits follow Conventional Commits (a git hook checks them) and releases are cut by release-please from those messages. Everyone taking part agrees to the Code of Conduct. Report security issues privately, as described in SECURITY.md.
Pinwren uses two licences:
packages/widget — are licensed under the
GNU Affero General Public License v3.0. You may use, modify and self-host them freely,
including commercially. If you modify Pinwren and let other people use it over a network, you must offer
them the source code of your modified version under the same licence.packages/widget, published as @pinwren/widget) is MIT-licensed,
so embedding it in your website or product places no obligations on your own code.Pinwren.open()Pinwren.on("submit", …)Pinwren.identify()Pinwren.setMetadata()Pinwren.show()hide()Pinwren.destroy()