Open-source Discord alternative — real-time voice + text chat
TypeScript
6
1,115 commits
updated Sep 17, 2026
Voice, chat and screen sharing for your people. Make a server, share the link. That's it.
pqp.gg · Download for desktop · Architecture · Deploy your own · Support the project
Open-source, Discord-like, and small enough to actually read. Use the hosted service at pqp.gg or self-host an independent copy — your own URL, your own database, your own rules.
Model: servers (invite links) → public/private channels → text + voice.
Roles are permission bits behind a seeded staff ladder (owner, admin, manager,
moderator, VIP, @everyone) plus whatever a server invents, with per-channel
overwrites. Usernames are name#1234; a claimed @handle is a public page.
| Voice channels | The server picks the media path per room, at the first join: peer-to-peer for DM calls and small servers, a LiveKit SFU for communities and servers of ten or more, so a crowded room is not capped at a handful. Push-to-talk, per-peer volume, device pickers. |
| Screen sharing | With the machine's audio. Two presenters at once peer-to-peer, four on the SFU. Send from a browser or the desktop app (Android in peer-to-peer rooms); watch from any client, phones included. |
| Chat | Markdown, replies, reactions, pins, edits, typing, unread and mention badges. Link previews, GIF picker. Outgoing webhooks after a human message (docs). |
| Search | Full-text across the server, stemmed for Portuguese and English, accent-insensitive. |
| DMs and groups | Direct messages and group DMs up to 10, found by handle. |
| Attachments | Images inline, video/audio as tap-to-play (nothing downloads until pressed). Direct-to-storage uploads; the API never touches the bytes. |
| Presence and status | Online / idle / do-not-disturb / invisible, with shape-differentiated dots. |
| Moderation | Timeouts, kick, ban (with voice ejection), delete, pin, in-app reporting with an evidence trail, per-server audit log. |
| Safety | 18+ age gate, image scanning hooks, LGPD self-serve export and deletion. |
| Two languages | pt-BR and English through the whole funnel, legal pages included — with drift between them failing CI. |
| Installable | PWA on mobile, signed + notarized desktop app with auto-update. |
Prereqs: Node 20+, pnpm 10, Docker (for Postgres).
pnpm install
cp .env.example .env
cp .env.example client/.env
docker compose up -d postgres
pnpm dev
# client http://localhost:5173 · api http://localhost:3001 · ws /ws
No Clerk account? Set DEV_AUTH_BYPASS=true in .env and
VITE_DEV_AUTH_BYPASS=true in client/.env, restart, and you're signed in as
a local dev user. The bypass refuses to run when NODE_ENV=production.
With Clerk: pnpm clerk:login && pnpm clerk:init && pnpm clerk:env — see
docs/CLERK_SETUP.md.
Everything lives in .env.example, documented inline. The ones that matter:
| Variable | What it does |
|---|---|
DATABASE_URL | Postgres. initDb() applies the schema on boot, idempotently. |
CLERK_SECRET_KEY / VITE_CLERK_PUBLISHABLE_KEY | Auth. |
CORS_ALLOWED_ORIGINS | Required on any public deploy — empty falls open to * for local dev. |
TRUST_PROXY | Set behind Railway/Fly/Cloudflare, or every client shares one rate-limit bucket. |
TURN_URL / TURN_USERNAME / TURN_CREDENTIAL | Voice across NATs. Served via /api/ice-servers. |
LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET | Turns on the SFU; the server advertises it, no client rebuild. |
S3_* | R2/MinIO attachments. Off entirely when unset. |
CONTENT_SCAN_PROVIDER / OPENAI_API_KEY | Image safety scanning at claim time. |
CLUSTER_BUS=postgres | Fans chat out over LISTEN/NOTIFY for multi-instance. Off = exactly today's single-process behaviour. |
VOICE_REGISTRY=postgres | Puts the voice peer map and transport pins in Postgres so rosters survive a second instance. Off by default; see docs/plans/MULTI_INSTANCE_VOICE.md. |
The server chooses per room, on the first join, and pins the choice for the
room's lifetime (server/src/voice/transport-policy.ts). No client rebuild
switches it, and there are no silent mesh/SFU splits: a client that cannot use
the room's transport is refused rather than left out of the call.
| Mode | When it is used | Capacity | Notes |
|---|---|---|---|
| Mesh | DM calls, and servers under ten members. Everything, on a deployment with no LIVEKIT_* | MESH_VOICE_LIMIT, 8 per channel | P2P; media never touches the server. TURN only across strict NATs. |
| LiveKit SFU | Listed communities of any size, and servers of ten or more | Big rooms, and live on pqp.gg | Implemented and verified end to end. |
| Cloudflare Realtime | never | n/a | Still a stub; falls back to mesh. |
cp .env.example .env # set DATABASE_URL, Clerk keys, TURN
docker compose up -d # api + ws + built client on :3001
Or deploy the API to Fly.io / Railway and the static client to Cloudflare Pages (docs/DEPLOY.md). A self-host is an independent copy — own URL, data, and Clerk instance, not linked to pqp.gg.
ios/ (SwiftUI, TestFlight beta at
pqp.gg/beta) and android/ (Kotlin and
Compose, APK beta at pqp.gg/android). Neither is on
a store yet. The PWA still installs from any browser
(docs/PWA.md).REST under /api/* (Bearer auth, rate-limited per identity) and a WebSocket at
/ws ({type:"auth",token} first, then chat, presence and voice signalling).
The full route list lives in server/src/api/index.ts;
the wire contracts are the Zod schemas in packages/shared/.
| Command | Description |
|---|---|
pnpm dev | Client + server |
pnpm test | Every package's suite (DB-backed tests need Postgres) |
pnpm typecheck / pnpm lint | The obvious |
pnpm load:chat | Chat throughput harness against a built server |
pnpm soak:voice | Voice connection soak |
pnpm electron:dev / electron:dist | Desktop shell against Vite / packaged build |
LIVEKIT_* tops out at 8 per voice channel, because
every room is then peer-to-peer. Cloudflare Realtime remains a stub.CLUSTER_BUS=postgres and voice rosters with VOICE_REGISTRY=postgres, but
the pieces are not yet enough to run two machines. Rate limits stay
per-instance either way.server/src/services/push.ts knows web and apns only, so a closed
Android app hears nothing.18+ only, self-declared at first use. In-app reporting routes DM reports to instance moderators, never server admins. The illegal-content risk assessment, content-safety runbook and moderation posture live in docs/RISK_ASSESSMENT.md and docs/CONTENT_SAFETY.md. Terms and privacy are bilingual at pqp.gg/terms and pqp.gg/privacy.
pqp.gg is built by two brothers and the hosting comes out of our pockets. If it is useful to you and you want to help with that bill, there is GitHub Sponsors and, for Pix, pqp.gg/apoie. A donation is a gift toward hosting costs: it unlocks nothing, there are no tiers and no perks, and self-hosting stays free with every feature.
Bug reports, questions and patches are all welcome, in English or Portuguese.
Start with CONTRIBUTING.md (em português):
it covers setup, the handful
of things in this repo that will otherwise waste an hour of your time, and what
the AGPL means for you if pqp ever charges money.
Copyright (C) 2026 rafaelcg.
This project is licensed under the GNU Affero General Public License v3.0
(AGPL-3.0-only). Anyone who modifies the software and runs it as a network
service must make the corresponding source available to users of that service.
TypeScript
70.3%
Swift
8.8%
Kotlin
7.7%
JavaScript
6.6%
Go
3.7%
HTML
1.1%
Open-source Discord alternative — real-time voice + text chat
TypeScript
6
1,115 commits
updated Sep 17, 2026
Voice, chat and screen sharing for your people. Make a server, share the link. That's it.
pqp.gg · Download for desktop · Architecture · Deploy your own · Support the project
Open-source, Discord-like, and small enough to actually read. Use the hosted service at pqp.gg or self-host an independent copy — your own URL, your own database, your own rules.
Model: servers (invite links) → public/private channels → text + voice.
Roles are permission bits behind a seeded staff ladder (owner, admin, manager,
moderator, VIP, @everyone) plus whatever a server invents, with per-channel
overwrites. Usernames are name#1234; a claimed @handle is a public page.
| Voice channels | The server picks the media path per room, at the first join: peer-to-peer for DM calls and small servers, a LiveKit SFU for communities and servers of ten or more, so a crowded room is not capped at a handful. Push-to-talk, per-peer volume, device pickers. |
| Screen sharing | With the machine's audio. Two presenters at once peer-to-peer, four on the SFU. Send from a browser or the desktop app (Android in peer-to-peer rooms); watch from any client, phones included. |
| Chat | Markdown, replies, reactions, pins, edits, typing, unread and mention badges. Link previews, GIF picker. Outgoing webhooks after a human message (docs). |
| Search | Full-text across the server, stemmed for Portuguese and English, accent-insensitive. |
| DMs and groups | Direct messages and group DMs up to 10, found by handle. |
| Attachments | Images inline, video/audio as tap-to-play (nothing downloads until pressed). Direct-to-storage uploads; the API never touches the bytes. |
| Presence and status | Online / idle / do-not-disturb / invisible, with shape-differentiated dots. |
| Moderation | Timeouts, kick, ban (with voice ejection), delete, pin, in-app reporting with an evidence trail, per-server audit log. |
| Safety | 18+ age gate, image scanning hooks, LGPD self-serve export and deletion. |
| Two languages | pt-BR and English through the whole funnel, legal pages included — with drift between them failing CI. |
| Installable | PWA on mobile, signed + notarized desktop app with auto-update. |
Prereqs: Node 20+, pnpm 10, Docker (for Postgres).
pnpm install
cp .env.example .env
cp .env.example client/.env
docker compose up -d postgres
pnpm dev
# client http://localhost:5173 · api http://localhost:3001 · ws /ws
No Clerk account? Set DEV_AUTH_BYPASS=true in .env and
VITE_DEV_AUTH_BYPASS=true in client/.env, restart, and you're signed in as
a local dev user. The bypass refuses to run when NODE_ENV=production.
With Clerk: pnpm clerk:login && pnpm clerk:init && pnpm clerk:env — see
docs/CLERK_SETUP.md.
Everything lives in .env.example, documented inline. The ones that matter:
| Variable | What it does |
|---|---|
DATABASE_URL | Postgres. initDb() applies the schema on boot, idempotently. |
CLERK_SECRET_KEY / VITE_CLERK_PUBLISHABLE_KEY | Auth. |
CORS_ALLOWED_ORIGINS | Required on any public deploy — empty falls open to * for local dev. |
TRUST_PROXY | Set behind Railway/Fly/Cloudflare, or every client shares one rate-limit bucket. |
TURN_URL / TURN_USERNAME / TURN_CREDENTIAL | Voice across NATs. Served via /api/ice-servers. |
LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET | Turns on the SFU; the server advertises it, no client rebuild. |
S3_* | R2/MinIO attachments. Off entirely when unset. |
CONTENT_SCAN_PROVIDER / OPENAI_API_KEY | Image safety scanning at claim time. |
CLUSTER_BUS=postgres | Fans chat out over LISTEN/NOTIFY for multi-instance. Off = exactly today's single-process behaviour. |
VOICE_REGISTRY=postgres | Puts the voice peer map and transport pins in Postgres so rosters survive a second instance. Off by default; see docs/plans/MULTI_INSTANCE_VOICE.md. |
The server chooses per room, on the first join, and pins the choice for the
room's lifetime (server/src/voice/transport-policy.ts). No client rebuild
switches it, and there are no silent mesh/SFU splits: a client that cannot use
the room's transport is refused rather than left out of the call.
| Mode | When it is used | Capacity | Notes |
|---|---|---|---|
| Mesh | DM calls, and servers under ten members. Everything, on a deployment with no LIVEKIT_* | MESH_VOICE_LIMIT, 8 per channel | P2P; media never touches the server. TURN only across strict NATs. |
| LiveKit SFU | Listed communities of any size, and servers of ten or more | Big rooms, and live on pqp.gg | Implemented and verified end to end. |
| Cloudflare Realtime | never | n/a | Still a stub; falls back to mesh. |
cp .env.example .env # set DATABASE_URL, Clerk keys, TURN
docker compose up -d # api + ws + built client on :3001
Or deploy the API to Fly.io / Railway and the static client to Cloudflare Pages (docs/DEPLOY.md). A self-host is an independent copy — own URL, data, and Clerk instance, not linked to pqp.gg.
ios/ (SwiftUI, TestFlight beta at
pqp.gg/beta) and android/ (Kotlin and
Compose, APK beta at pqp.gg/android). Neither is on
a store yet. The PWA still installs from any browser
(docs/PWA.md).REST under /api/* (Bearer auth, rate-limited per identity) and a WebSocket at
/ws ({type:"auth",token} first, then chat, presence and voice signalling).
The full route list lives in server/src/api/index.ts;
the wire contracts are the Zod schemas in packages/shared/.
| Command | Description |
|---|---|
pnpm dev | Client + server |
pnpm test | Every package's suite (DB-backed tests need Postgres) |
pnpm typecheck / pnpm lint | The obvious |
pnpm load:chat | Chat throughput harness against a built server |
pnpm soak:voice | Voice connection soak |
pnpm electron:dev / electron:dist | Desktop shell against Vite / packaged build |
LIVEKIT_* tops out at 8 per voice channel, because
every room is then peer-to-peer. Cloudflare Realtime remains a stub.CLUSTER_BUS=postgres and voice rosters with VOICE_REGISTRY=postgres, but
the pieces are not yet enough to run two machines. Rate limits stay
per-instance either way.server/src/services/push.ts knows web and apns only, so a closed
Android app hears nothing.18+ only, self-declared at first use. In-app reporting routes DM reports to instance moderators, never server admins. The illegal-content risk assessment, content-safety runbook and moderation posture live in docs/RISK_ASSESSMENT.md and docs/CONTENT_SAFETY.md. Terms and privacy are bilingual at pqp.gg/terms and pqp.gg/privacy.
pqp.gg is built by two brothers and the hosting comes out of our pockets. If it is useful to you and you want to help with that bill, there is GitHub Sponsors and, for Pix, pqp.gg/apoie. A donation is a gift toward hosting costs: it unlocks nothing, there are no tiers and no perks, and self-hosting stays free with every feature.
Bug reports, questions and patches are all welcome, in English or Portuguese.
Start with CONTRIBUTING.md (em português):
it covers setup, the handful
of things in this repo that will otherwise waste an hour of your time, and what
the AGPL means for you if pqp ever charges money.
Copyright (C) 2026 rafaelcg.
This project is licensed under the GNU Affero General Public License v3.0
(AGPL-3.0-only). Anyone who modifies the software and runs it as a network
service must make the corresponding source available to users of that service.
TypeScript
70.3%
Swift
8.8%
Kotlin
7.7%
JavaScript
6.6%
Go
3.7%
HTML
1.1%