noun: a bundle; in mathematics, a structure that describes how local pieces fit together into a coherent whole.
Open-source plural system tracking. A self-hostable replacement for SimplyPlural, built with data security and sustainability in mind.
Status: selfhostable and usable — hosted service is still being set up. Feedback welcome via issues or our Discord.
SimplyPlural is shutting down. Many alternatives are either incomplete, closed-source, local-only, or lack credible infrastructure foundations. Sheaf is built by people who are actually paid to run things at scale, with a focus on:
- Data security — Email and TOTP secrets are encrypted at rest (application-level). All data is GDPR Article 9 special category data and is treated accordingly.
- Self-hosting first —
docker compose upand you have your own instance - Sustainable economics — Designed from day 1 to support both selfhosting and an optional hosted tier without forking the codebase or using proprietary extensions
- Contributor accessibility — Python/FastAPI backend, React frontend.
- Members — Profiles with name, pronouns, description, colour, birthday, avatar, privacy levels
- Front tracking — Log switches, including cofronters and custom fronts
- Groups — Organize members into groups with nesting (subsystems)
- Tags — Flexible member tagging
- Custom fields — Define your own fields (text, number, date, boolean, select) with per-field privacy
- Journals — Per-member or system-wide markdown journal entries with edit history
- Revision history — Member bios and journal entries are versioned, with tier-aware retention caps
- Revision pinning — Pin specific revisions to protect them from automatic trim, with optional re-auth + grace on unpin
- System Safety — Optional grace period and re-auth (password / TOTP) on destructive actions (member/journal/group/etc deletion, revision unpin)
- SimplyPlural import — Import your SP export with granular control (select specific members, toggle front history, etc.)
- File storage — File uploads with filesystem or S3-compatible backends
- Data export
- 2FA — Optional TOTP with recovery codes
- API keys — Scoped, named keys (
sk_…) for scripts and integrations - Admin dashboard — User management, invite codes, storage audit, background job monitoring, optional step-up auth
- Registration modes — Open, approval-required, invite-only, or closed
- Email verification — Optional required verification with configurable flow
- Account deletion — Self-service with configurable grace period
- Field-level encryption — Member names/bios, journal titles/bodies, and revision history encrypted at rest with XChaCha20-Poly1305
- Eye-friendly — Default dark, with Dark Reader compatibility and a clear light toggle
See FAQ.md
cp .env.example .env
# Edit .env — at minimum, change POSTGRES_PASSWORD and JWT_SECRET_KEY
docker compose up -dThe API is available at http://localhost:8000 with interactive docs at http://localhost:8000/v1/docs.
# JWT secret
python -c "import secrets; print(secrets.token_urlsafe(32))"
# Encryption key (optional — auto-generated on first start if not set)
python -c "import secrets; print(secrets.token_hex(32))"Important: If you let Sheaf auto-generate the encryption key, it's saved to
data/encryption.keyinside the Docker volume. Back this up. If you lose it, all sensiive user data (emails, TOTP secrets, and all member information) is unrecoverable.
The web UI is a React SPA in web/. For development:
cd web
npm install
npm run devThis starts Vite's dev server on http://localhost:5173 with a proxy to the API at :8000.
sheaf/
├── sheaf/ # Python backend
│ ├── main.py # FastAPI app with lifespan management
│ ├── config.py # Pydantic Settings (twelve-factor config)
│ ├── models/ # SQLAlchemy 2.0 async models
│ ├── schemas/ # Pydantic request/response models
│ ├── api/v1/ # Versioned API routes
│ ├── auth/ # JWT, sessions, TOTP, password hashing
│ ├── storage/ # File storage abstraction (filesystem/S3)
│ └── services/ # Business logic (retention, import)
├── web/ # React + TypeScript + Vite + Tailwind
├── alembic/ # Database migrations
├── tests/ # pytest test suite
├── docs/ # Self-hosting and client development guides
├── Dockerfile
├── docker-compose.yml
└── .env.example
Tech stack: Python 3.12+, FastAPI, SQLAlchemy 2.0 (async), PostgreSQL 16, Redis, Alembic. Frontend: React 19, TypeScript, Vite, Tailwind CSS v4, shadcn/ui. Field-level encryption with XChaCha20-Poly1305 (libsodium).
All endpoints are under /v1/. The OpenAPI spec is auto-generated at /v1/openapi.json.
Auth: Two methods are supported:
- JWT bearer tokens (15min access + 30d refresh) — for interactive clients.
POST /v1/auth/loginreturns tokens; pass asAuthorization: Bearer <token>. - API keys (
sk_…prefixed) — for scripts and integrations. Create in Settings; pass asAuthorization: Bearer sk_…. Keys are scoped (e.g.members:read,members:write) and never expose the plaintext after creation.
Key endpoints:
| Endpoint | Description |
|---|---|
POST /v1/auth/register |
Create account |
POST /v1/auth/login |
Login, get tokens |
GET /v1/auth/me |
Current user info |
GET/POST /v1/auth/keys |
List/create API keys |
DELETE /v1/auth/keys/{id} |
Revoke API key |
GET /v1/systems/me |
Your system profile |
GET/POST /v1/members |
List/create members |
GET/POST /v1/fronts |
Front history |
GET /v1/fronts/current |
Who's fronting now |
GET/POST /v1/groups |
Groups |
GET/POST /v1/tags |
Tags |
GET/POST /v1/fields |
Custom field definitions |
PUT /v1/members/{id}/fields |
Set custom field values |
GET/POST /v1/journals |
List/create journal entries |
GET /v1/journals/{id}/revisions |
Edit history for an entry |
POST /v1/journals/{id}/pin-revision |
Pin a revision (exempt from trim) |
POST /v1/journals/{id}/unpin-revision |
Unpin (immediate or queued behind grace) |
GET/PATCH /v1/system/safety |
System Safety settings + pending actions |
POST /v1/import/simplyplural |
Import SP data |
POST /v1/import/sheaf |
Import Sheaf export |
GET /v1/export |
Export all data |
POST /v1/files/upload |
Upload avatar |
Full interactive docs: http://your-instance/v1/docs
Building a client? See docs/CLIENT_DESIGN.md for the complete client development guide — auth flows, scopes, session management, client settings storage, and all endpoints.
cp .env.example .env
# Edit .env — at minimum, change POSTGRES_PASSWORD and JWT_SECRET_KEY
docker compose up -dSee docs/SELFHOSTING.md for the full guide covering:
- Secrets and encryption key management
- Admin access and step-up authentication
- Optional dependencies (S3, SMTP, SES, SendGrid)
- Email configuration (SMTP / AWS SES / SendGrid) with bounce/complaint handling
- Registration modes (open / approval / invite / closed) and email verification
- Account deletion with configurable grace period
- File storage (filesystem / S3) with hotlink protection
- Storage quotas and upload limits
- Revision-history retention caps and pinned-revision tier knobs
- System Safety (destructive-action grace, re-auth, per-category toggles)
- Frontend build and serving
- Reverse proxy setup (nginx, Caddy) and the
SHEAF_BASE_URL/ cookie-Secure relationship - Rate limiting and trusted proxies
- Public test / demo mode (periodic non-admin wipe + warning banner)
- Backups
Sheaf publishes signed Docker images and a verifiable frontend bundle so users can confirm a running instance corresponds to the public source. Image signatures use sigstore/cosign keyless OIDC (no key material to manage; signatures tied to the GitHub Actions workflow identity, recorded in Rekor's public transparency log). The frontend ships with Subresource Integrity hashes and a published build manifest, so a browser-side verifier can confirm byte-for-byte that loaded JavaScript matches the published source.
See docs/VERIFYING.md for the trust model, how to run cosign verify, how to compare the served build-manifest.json against your own npm run build, and what the design explicitly does not claim (no hardware attestation; backend behaviour beyond the served frontend is operator-attested).
# Backend
pip install -e ".[dev]"
docker compose up db redis -d
alembic upgrade head
uvicorn sheaf.main:app --reload
# Frontend
cd web && npm install && npm run dev
# Full test suite (spins up an isolated Docker stack, tests all server configs)
./run_tests.sh
# Quick run against an already-running local server
# SHEAF_TEST_DB_URL needed so the admin fixture can reach Postgres directly
SHEAF_TEST_DB_URL=postgresql+asyncpg://sheaf:<POSTGRES_PASSWORD>@localhost:5432/sheaf pytest- Named fronts — save a named combination of members and make them searchable in the start front dialog
- CLI similar to simplyplural-cli
- Front change notifications (WebSocket push)
- Journals/notes (per-member, encrypted at rest)
- PluralKit bidirectional sync
- Friend/trust system (cross-system visibility controls)
- Per-field-per-member privacy overrides
- Storage quotas (per-tier account-wide budget)
- Orphaned file cleanup (images uploaded but never attached to a member/system)
- API keys with granular scopes (for scripts and integrations)
- Admin UI (user management, maintenance operations)
- Signed image URLs with S3 presign support (hotlink protection)
- Webhooks for switch/fronter notification
- Custom-defined user tiers by server admin instead of placeholder free/plus/selfhosted
- Android+iOS apps (in progress — API-first, OpenAPI spec available for client generation)
- Prometheus-compatible /metrics endpoint
- Terraform module for cloud deployment
- More 2FA methods — WebAuthn/YubiKey, email OTP as a "better than nothing" fallback
- Alternate secrets management methods - AWS Secrets Manager, Vault, others?
- Accessibility improvements - image alt text support, additional TBD
This means: you can self-host, modify, and run Sheaf however you want. If you run a modified version as a public service, you must share your modifications under the same license.