rafaelcg/pqp

Open-source Discord alternative — real-time voice + text chat

TypeScript

6

1,115 commits

updated Sep 17, 2026

See the code

README

pqp

Voice, chat and screen sharing for your people. Make a server, share the link. That's it.

CI Release Sponsor

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.

What it does

Voice channelsThe 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 sharingWith 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.
ChatMarkdown, replies, reactions, pins, edits, typing, unread and mention badges. Link previews, GIF picker. Outgoing webhooks after a human message (docs).
SearchFull-text across the server, stemmed for Portuguese and English, accent-insensitive.
DMs and groupsDirect messages and group DMs up to 10, found by handle.
AttachmentsImages inline, video/audio as tap-to-play (nothing downloads until pressed). Direct-to-storage uploads; the API never touches the bytes.
Presence and statusOnline / idle / do-not-disturb / invisible, with shape-differentiated dots.
ModerationTimeouts, kick, ban (with voice ejection), delete, pin, in-app reporting with an evidence trail, per-server audit log.
Safety18+ age gate, image scanning hooks, LGPD self-serve export and deletion.
Two languagespt-BR and English through the whole funnel, legal pages included — with drift between them failing CI.
InstallablePWA on mobile, signed + notarized desktop app with auto-update.

Quick start (development)

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.

Configuration

Everything lives in .env.example, documented inline. The ones that matter:

VariableWhat it does
DATABASE_URLPostgres. initDb() applies the schema on boot, idempotently.
CLERK_SECRET_KEY / VITE_CLERK_PUBLISHABLE_KEYAuth.
CORS_ALLOWED_ORIGINSRequired on any public deploy — empty falls open to * for local dev.
TRUST_PROXYSet behind Railway/Fly/Cloudflare, or every client shares one rate-limit bucket.
TURN_URL / TURN_USERNAME / TURN_CREDENTIALVoice across NATs. Served via /api/ice-servers.
LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRETTurns on the SFU; the server advertises it, no client rebuild.
S3_*R2/MinIO attachments. Off entirely when unset.
CONTENT_SCAN_PROVIDER / OPENAI_API_KEYImage safety scanning at claim time.
CLUSTER_BUS=postgresFans chat out over LISTEN/NOTIFY for multi-instance. Off = exactly today's single-process behaviour.
VOICE_REGISTRY=postgresPuts 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.

Voice backends

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.

ModeWhen it is usedCapacityNotes
MeshDM calls, and servers under ten members. Everything, on a deployment with no LIVEKIT_*MESH_VOICE_LIMIT, 8 per channelP2P; media never touches the server. TURN only across strict NATs.
LiveKit SFUListed communities of any size, and servers of ten or moreBig rooms, and live on pqp.ggImplemented and verified end to end.
Cloudflare Realtimenevern/aStill a stub; falls back to mesh.

Self-host

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.

Desktop and mobile

API

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/.

Scripts

CommandDescription
pnpm devClient + server
pnpm testEvery package's suite (DB-backed tests need Postgres)
pnpm typecheck / pnpm lintThe obvious
pnpm load:chatChat throughput harness against a built server
pnpm soak:voiceVoice connection soak
pnpm electron:dev / electron:distDesktop shell against Vite / packaged build

Known limitations

  • A deployment with no LIVEKIT_* tops out at 8 per voice channel, because every room is then peer-to-peer. Cloudflare Realtime remains a stub.
  • Single instance unless you opt in. Chat can go multi-instance with 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.
  • Android has no push server leg. Web Push and APNs are wired end to end; server/src/services/push.ts knows web and apns only, so a closed Android app hears nothing.
  • Android has no camera in either direction, and cannot send a screen share into a large room.
  • No AutoMod, no forwarding on the phones. See docs/DISCORD_GAPS.md for the honest census and docs/PARITY.md for the client-by-client matrix.

Trust & safety

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.

Apoie o projeto / Support the project

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.

Contributing

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.

License

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.

Contributors

rafaelcg

876 commits

AndreCamm

211 commits

claude

18 commits

cursoragent

10 commits

rafaelcg/pqp

Open-source Discord alternative — real-time voice + text chat

TypeScript

6

1,115 commits

updated Sep 17, 2026

See the code

README

pqp

Voice, chat and screen sharing for your people. Make a server, share the link. That's it.

CI Release Sponsor

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.

What it does

Voice channelsThe 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 sharingWith 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.
ChatMarkdown, replies, reactions, pins, edits, typing, unread and mention badges. Link previews, GIF picker. Outgoing webhooks after a human message (docs).
SearchFull-text across the server, stemmed for Portuguese and English, accent-insensitive.
DMs and groupsDirect messages and group DMs up to 10, found by handle.
AttachmentsImages inline, video/audio as tap-to-play (nothing downloads until pressed). Direct-to-storage uploads; the API never touches the bytes.
Presence and statusOnline / idle / do-not-disturb / invisible, with shape-differentiated dots.
ModerationTimeouts, kick, ban (with voice ejection), delete, pin, in-app reporting with an evidence trail, per-server audit log.
Safety18+ age gate, image scanning hooks, LGPD self-serve export and deletion.
Two languagespt-BR and English through the whole funnel, legal pages included — with drift between them failing CI.
InstallablePWA on mobile, signed + notarized desktop app with auto-update.

Quick start (development)

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.

Configuration

Everything lives in .env.example, documented inline. The ones that matter:

VariableWhat it does
DATABASE_URLPostgres. initDb() applies the schema on boot, idempotently.
CLERK_SECRET_KEY / VITE_CLERK_PUBLISHABLE_KEYAuth.
CORS_ALLOWED_ORIGINSRequired on any public deploy — empty falls open to * for local dev.
TRUST_PROXYSet behind Railway/Fly/Cloudflare, or every client shares one rate-limit bucket.
TURN_URL / TURN_USERNAME / TURN_CREDENTIALVoice across NATs. Served via /api/ice-servers.
LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRETTurns on the SFU; the server advertises it, no client rebuild.
S3_*R2/MinIO attachments. Off entirely when unset.
CONTENT_SCAN_PROVIDER / OPENAI_API_KEYImage safety scanning at claim time.
CLUSTER_BUS=postgresFans chat out over LISTEN/NOTIFY for multi-instance. Off = exactly today's single-process behaviour.
VOICE_REGISTRY=postgresPuts 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.

Voice backends

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.

ModeWhen it is usedCapacityNotes
MeshDM calls, and servers under ten members. Everything, on a deployment with no LIVEKIT_*MESH_VOICE_LIMIT, 8 per channelP2P; media never touches the server. TURN only across strict NATs.
LiveKit SFUListed communities of any size, and servers of ten or moreBig rooms, and live on pqp.ggImplemented and verified end to end.
Cloudflare Realtimenevern/aStill a stub; falls back to mesh.

Self-host

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.

Desktop and mobile

API

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/.

Scripts

CommandDescription
pnpm devClient + server
pnpm testEvery package's suite (DB-backed tests need Postgres)
pnpm typecheck / pnpm lintThe obvious
pnpm load:chatChat throughput harness against a built server
pnpm soak:voiceVoice connection soak
pnpm electron:dev / electron:distDesktop shell against Vite / packaged build

Known limitations

  • A deployment with no LIVEKIT_* tops out at 8 per voice channel, because every room is then peer-to-peer. Cloudflare Realtime remains a stub.
  • Single instance unless you opt in. Chat can go multi-instance with 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.
  • Android has no push server leg. Web Push and APNs are wired end to end; server/src/services/push.ts knows web and apns only, so a closed Android app hears nothing.
  • Android has no camera in either direction, and cannot send a screen share into a large room.
  • No AutoMod, no forwarding on the phones. See docs/DISCORD_GAPS.md for the honest census and docs/PARITY.md for the client-by-client matrix.

Trust & safety

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.

Apoie o projeto / Support the project

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.

Contributing

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.

License

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.

Contributors

rafaelcg

876 commits

AndreCamm

211 commits

claude

18 commits

cursoragent

10 commits

Languages

TypeScript

70.3%

Swift

8.8%

Kotlin

7.7%

JavaScript

6.6%

Go

3.7%

HTML

1.1%