Project management your users can see.
Sayr is a source-available project management platform with a built-in public-facing portal. Your team works internally while users submit feedback, vote on features, and track progress — all from the same tool.
Teams juggle a project management tool and a separate customer feedback tool — duplicated work, stale updates, and users who never know what's happening.
Sayr replaces both with one board, two views: your team sees everything, the public sees only what you allow.
- Tasks with statuses: Backlog, Todo, In Progress, Done, Canceled
- 5 priority levels: Urgent, High, Medium, Low, None
- Multiple assignees per task
- Subtasks — one level deep
- Task relations — Blocks, Depends on, Related to
- Labels — color-coded, multiple per task, with own visibility settings
- Categories — group tasks and route to GitHub repositories
- Releases — milestones/versions with progress charts, assignee workload, and priority distribution
- Saved views — filtered/grouped views persisted per organization
- Issue templates — pre-fill title, description, labels, assignees, category, priority, status, release, and visibility
- Kanban and list views
- Bulk operations (status changes, label assignment)
- Full audit timeline — every change recorded
The core differentiator. Every item carries its own public/private toggle: tasks, comments (public vs. internal), labels, and timeline entries. Changes take effect immediately.
- Each organization gets a dedicated subdomain (
{org}.sayr.ioon cloud, or your own domain when self-hosting) - Live task board showing public tasks with sorting (Most Popular, Newest, Trending)
- User voting on tasks (real-time counts)
- Public task submission by signed-in visitors, optionally from a template
- Public comments with reactions; member badge distinguishes official responses
- Real-time updates via WebSocket
- Organization branding (banner, logo, name, description)
- Connect one or more GitHub organizations via GitHub App
- Bidirectional task ↔ issue sync, per repository, optionally scoped by category
- Keyword references in commits and PRs:
Ref #N,Fixes #N,Closes #N,Resolves #N - Commit and PR lifecycle tracking appear on the task timeline
- Comment syncing from GitHub to Sayr
- Respects repository visibility (private repo activity stays internal)
- Rich text editor (ProseKit) with headings, lists, code blocks, images, and @mentions
- Threaded comments with edit history and emoji reactions
- WebSocket broadcasting for all create/update/delete events
- Notifications and inbox
- My Tasks — cross-organization view of all assigned work
- GitHub OAuth login
- Email + password authentication
- Two-factor authentication (TOTP + backup codes)
- Passkeys — Face ID, Touch ID, Windows Hello, hardware security keys (WebAuthn)
- Active session management — view and revoke individual sessions
- Members and teams with granular per-permission controls
- Permission categories: Organization, Content Settings, Tasks, Moderation
- "Most permissive wins" model across team memberships
- Blocked users list
- REST API with OpenAPI spec (served via Scalar)
- Bearer token authentication
- Rate limiting: 100 req/min authenticated, 10 req/min unauthenticated
Sayr ships in three editions. Docker images are edition-locked at build time.
| Community | Cloud | Enterprise | |
|---|---|---|---|
| For | Free self-hosting | Hosted sayr.io | Licensed self-hosting |
| Organizations | 1 | Unlimited | Unlimited |
| Members | Unlimited | Free: 5 / Pro: unlimited | Unlimited |
| Releases | Unlimited | Free: 0 / Pro: unlimited | Unlimited |
| Saved views | Unlimited | Free: 3 / Pro: unlimited | Unlimited |
| Issue templates | Unlimited | Free: 3 / Pro: unlimited | Unlimited |
| Teams | Unlimited | Free: 1 / Pro: unlimited | Unlimited |
| Availability | Now | Now | Coming soon |
Community Edition images are published at ghcr.io/dorasto/sayr-ce-*.
| Technology | Purpose |
|---|---|
| TanStack Start | Full-stack React framework (SSR + server functions) |
| React | UI library |
| TanStack Router | Type-safe file-based routing |
| TanStack Query | Server state management |
| Tailwind CSS | Utility-first CSS |
| Shadcn/ui | Accessible component library (Radix primitives) |
| ProseKit | Block-based rich text editor (ProseMirror) |
| Vite | Build tool |
| Technology | Purpose |
|---|---|
| Bun | JavaScript runtime |
| Hono | Lightweight web framework (REST + WebSocket) |
| Drizzle ORM | TypeScript ORM |
| PostgreSQL | Primary database |
| Redis | Job queue and caching |
| MinIO | S3-compatible object storage |
| Zod | Validation |
| Technology | Purpose |
|---|---|
| Turborepo | Monorepo build orchestration |
| pnpm | Package manager |
| Biome | Linting and formatting |
| TypeScript | Type-safe JavaScript throughout |
| Docker | Containerization |
| Nginx | Reverse proxy and subdomain routing |
sayr/
├── apps/
│ ├── backend/ # Hono API server (Bun runtime, port 5468)
│ ├── marketing/ # Astro marketing site + Starlight docs (port 3001)
│ ├── nginx/ # Nginx reverse-proxy config
│ ├── start/ # TanStack Start frontend (port 3000)
│ ├── traefik/ # Traefik TLS termination config
│ └── worker/ # Background job processor (GitHub webhooks, cron)
└── packages/
├── auth/ # Better Auth configuration
├── database/ # Drizzle ORM schemas and CRUD functions
├── edition/ # Edition detection, capabilities, and plan limits
├── opentelemetry/ # Tracing and observability utilities
├── queue/ # Job queue abstraction (Redis or file-based)
├── storage/ # MinIO/S3 client with obfuscated filenames
├── ui/ # Shared Shadcn/ui component library
├── util/ # Shared utilities (slugs, date formatting, CDN URLs)
└── typescript-config/# Shared TypeScript configurations
- Bun 1.2+
- Node.js 22+ (LTS)
- pnpm 10.6+ (
npm install -g pnpm) - PostgreSQL 15+
- Redis (required for the job queue in production)
- Docker (optional, for containerized development)
| Command | Description |
|---|---|
pnpm dev |
Start all apps in development mode |
pnpm build |
Build all apps for production |
pnpm lint |
Run Biome linting |
pnpm lint:fix |
Fix linting issues |
pnpm format-write |
Format code with Biome |
pnpm check-types |
TypeScript type checking |
| Command | Description |
|---|---|
pnpm -F backend dev |
Backend only |
pnpm -F start dev |
Frontend only |
pnpm -F marketing dev |
Marketing site only |
pnpm -F worker dev |
Worker only |
pnpm -F @repo/database generate |
Generate database schema |
pnpm -F @repo/database migrate |
Apply schema migrations |
pnpm -F @repo/database db:studio |
Open Drizzle Studio |
pnpm -F start test |
Run tests (Vitest) |
Sayr supports extensible integrations that can add custom API routes and UI pages.
Use the create-integration scaffolding tool:
pnpm create-integration <integration-name> -a <author-name> -d <integration-description>This generates a new integration in packages/integrations/ with:
- API Routes — Custom REST endpoints (
/api/integrations/:orgId/:integrationId/...) - UI Pages — Admin pages rendered from config (cards, lists, tabs, grids)
- Settings — Per-organization configuration
my-integration/
├── api/
│ └── index.ts # API route handlers
├── src/
│ └── index.ts # Business logic, commands, utilities
├── ui/
│ ├── pages.ts # UI page config (settings, items, sync, etc.)
│ ├── renderer.tsx # Reusable UI components
│ └── components/ # Custom React components
├── integration.ts # Manifest registration
├── docs.ts # Documentation
└── README.md # Integration-specific docs
Pages are defined with a declarative config:
const settingsPage: UIPage = {
title: "Settings",
layout: "admin",
api: {
path: "/settings",
methods: { get: {}, patch: {} },
},
sections: [
{
type: "card",
title: "Configuration",
fields: [
{ name: "apiKey", type: "string", label: "API Key", required: true },
{ name: "enabled", type: "boolean", label: "Enabled" },
],
actions: [{ type: "save", label: "Save" }],
},
],
};| Type | Description |
|---|---|
card |
Card with fields and optional actions |
list |
Table or card list with CRUD actions |
tabs |
Tabbed sections |
grid |
Multi-column grid layout |
string,number— Text/number inputboolean— Checkboxselect— Dropdown with optionstextarea— Multi-line textreadonly— Display-only fieldheading,label— Typography
Use bind to extract nested data from API responses:
{ name: "status", type: "readonly", bind: "$.preview.status" }
{ name: "data", type: "readonly", bind: "$.preview.stringify" } // JSON stringifyexport INTEGRATION_MYINTEGRATION_ENABLED=trueThen configure per-organization in the admin UI.
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes following the code style guidelines below
- Push and open a Pull Request
- Indentation: Tabs, width 3
- Line width: 120 characters
- Quotes: Double quotes, semicolons always
- Imports: Use
@repo/*for workspace packages,@/for local app imports - TypeScript: Strict mode, avoid
any, useimport typefor type-only imports - Naming: camelCase for variables/functions, PascalCase for components/types
We follow Conventional Commits:
feat:— New featuresfix:— Bug fixesdocs:— Documentation changesstyle:— Formatting onlyrefactor:— Code refactoringtest:— Testschore:— Maintenance
- Turborepo for monorepo tooling
- Shadcn/ui for accessible components
- Better Auth for authentication
- Drizzle ORM for type-safe database operations
- Hono for the fast, lightweight web framework
- TanStack for the excellent full-stack toolkit
Made with care by Doras Media Ltd