A self-hosted member platform for organizations.
Members, projects, pillars, and organisational info — one app, one process, your brand.
Features · Quick Start · Make It Yours · Deployment · Contributing
Originally built for EMSA Macedonia (European Medical Students' Association), now open source. The name, logo, brand color, and labels are all configurable — any organization can run it as its own.
- Public site — landing page, project pages with image galleries, organisational pillars, board positions, and a scrolling supporter logo strip, all editable from the admin panel
- Members — email/password and Google OAuth login, profiles with avatars, admin-defined custom profile fields with a completion gate
- Projects — registration windows, participant caps, self-service sign-up, attendance tracking
- Roles —
USER→ADMIN→SUPER_ADMIN, enforced server-side; the first registered account becomes the instance owner - Rebrandable — organization name, logo, tagline, socials, and even the "pillars" label (call them committees, departments, chapters, ...) are editable at runtime
- Self-contained — a single Bun process serves both the API and the React SPA; images upload straight to any S3-compatible storage via presigned URLs
Bun · React 19 + React Router v7 · Tailwind CSS v4 + shadcn/ui · TanStack Query · React Hook Form + Zod · Drizzle ORM + PostgreSQL · Tiptap · Motion · JWT + Google OAuth · Cloudflare R2 (or any S3-compatible storage) · Resend
You'll need Bun >= 1.3.14 and a PostgreSQL database. For the full experience also grab an S3-compatible bucket (e.g. Cloudflare R2), Google OAuth credentials, and a Resend API key.
git clone https://github.com/AtanasovskiPetar/emsa.git
cd emsa
bun install
cp .env.example .env # fill in your values — validated at startup in src/lib/env.ts
bunx drizzle-kit migrate # apply database migrations
bun dev # http://localhost:3000Register your first account — on a fresh install it automatically becomes SUPER_ADMIN, so the instance has an owner from day one.
For production:
bun run build
bun startAlmost everything is configured at runtime from the Organization admin page: name, logo, tagline, description, socials, board positions, supporters, custom member fields, and the label used for pillars.
The only code change for a full rebrand is the brand color (default: red), in two places:
styles/globals.css— the--primaryand--sidebar-primarytoken pairs in both the:rootand.darkblockssrc/lib/email.ts— thethemeobject used by transactional email templates
A production Dockerfile is included. There's also a complete Pulumi setup that provisions a Hetzner VPS with Traefik and deploys per-environment with Docker Compose — batteries included, but entirely optional.
Contributions are welcome! To get set up:
bun install
bun dev # dev server with hot reload
bun run lint # ESLint
bun run prettier # formatThe codebase conventions live in CLAUDE.md — worth a skim before opening a PR.
A significant portion of this codebase was built with the assistance of Claude (Anthropic). All generated code has been reviewed, tested, and adapted to fit the project's requirements.
