Sheaf

Features

Sheaf aims for full feature-compatibility with SimplyPlural and beyond, without sacrificing correctness or security for ease of implementation. See the screenshots page for what each of these looks like in practice.

Members & systems

Members list

Members

Profiles with name, pronouns, description, colour, birthday, avatar, emoji, per-member privacy levels, and an optional PluralKit ID.

Custom fronts

Non-member fronting entities, such as a combined member state, or a status like "Asleep", "Away", or "Out" - show up in the fronter list without inflating member counts or analytics. Created fronting-private by default, so a public profile can't broadcast that you're asleep unless you deliberately decide otherwise.

Groups & subsystems

Organise members into groups, nested up to 8 levels deep for subsystem structure. The Groups page is a drag-to-reparent tree, and group filters are subtree-inclusive - picking a parent matches everyone in its subgroups too, with the subgroups surfaced so you can narrow down. Arrange them in whatever order you like; that order is respected everywhere groups are listed, travels with your backups, and carries through to shared pages.

Relationships

Record typed relationships between members, and between subsystems: partner, parent/child, protector, or any type you define. Types carry a direction mode - symmetric, directional, or either - so a two-way relationship never needs two entries, and can carry a colour that tints the graph. The Relationships page maps the whole system as a self-arranging graph you can pan, zoom, and edit directly by clicking the line between two members, behind an Edit switch that stays off by default so the graph is something you read until you say otherwise. Each relationship carries its own privacy level, and deleting a type can be put behind System Safety, since it takes every relationship drawn with it.

Archived members

Soft-hide a member without deleting anything. They drop out of the members list, front switcher, top-fronters, and pickers, but still render everywhere they appear in history, so a name is never lost. Restorable any time, and optionally gated behind System Safety re-auth.

Tags

Flexible tagging to slice and filter your members however you like.

Custom fields configuration

Custom fields

Define your own fields - text, number, date, boolean, select - with a per-field privacy level that governs whether the field can appear on anything you share. Arrange them in the order you want; that order travels with your backups and carries through to shared pages.

Fronting

Dashboard with current fronters

Front tracking

Log switches, cofronters, and custom fronts. Query who's fronting now or browse full history.

Status notes

Attach a free-text note to a fronting period (encrypted at rest) for context the rest of the system might want.

Analytics with per-member front time and hour-of-day chart

Analytics

Per-member front time, percent of window, session count, longest session, and hour-of-day distribution. Configurable window: 7d / 30d / 90d / 1 year. Co-fronting is double-counted so individual stats stay accurate.

Reminders configuration

Reminders

Daily, weekly, or monthly pings, or fire reminders X minutes after a specific member fronts. Member-scoped reminders queue while nobody on the list is fronting and drain as a digest when one switches in.

Notifications configuration

Front notifications

Tell friends, partners, your other devices, or your monitoring stack when fronts change. Channels: web push, mobile push (FCM and APNs), generic webhook (JSON / Discord / Slack / plaintext), ntfy, and Pushover. Per-channel filters with three-layer member visibility (base + group rules + per-member overrides), payload sensitivity, debounce, quiet hours, and an aggregation window that collapses a flurry of switches into one notification carrying the net result. You control how a member the channel can't see appears when they're fronting alongside one it can: named as "1 other", vaguer as "someone", or suppressing the notification entirely.

Realtime front stream

A Server-Sent Events endpoint (GET /v1/fronts/stream) pushes your own front changes as they happen instead of making you poll. Aimed at home automation - Home Assistant, Node-RED - and anything else that wants live state. The client dials out and holds the connection open, so it works from a LAN-only consumer no webhook could ever reach. Authenticated with an API key carrying fronts:read, and it exposes nothing you couldn't already read from the API.

Inside the system

Polls

Polls

Run a vote across the system. Single or multi-choice, live or hidden results, hard deadline with auto-purge. Each vote is attributed to a member who must be in the current front, with a full audit log and fronting snapshot.

Messages board and walls

Messages

Global system message board plus a per-member wall, so headmates can leave each other notes inside the system. Replies chain, edits keep history, deletes are soft and gated by System Safety.

Notes

Lightweight scratchpad per member and per system. Markdown, encrypted at rest. No revision history by design - for "trigger list / favourite drink / current med doses" quick reference.

Journals

Journals

Per-member or system-wide markdown journal entries. Encrypted at rest, with versioned edit history.

Sharing

Showing part of your system to people outside it, without handing over the rest. Off unless your instance's operator enables it, and off for you until you deliberately publish something.

Views and grants

A view is a named, curated selection of exactly which members, custom fields, groups, and relationships are shown. A grant points an audience at a view: either a public profile, or an opaque share link that carries nothing identifying which system it belongs to and can be revoked or rotated whenever you like. Nothing is ever shown that you didn't deliberately add, and nothing is reachable at all until you create a grant.

Privacy levels that mean something

Members, groups, custom fields, and individual relationships each carry their own private / friends / public level, and your system-wide setting is the ceiling over all of it. Public doesn't mean published: it means "I'm willing for this to be shown", and it's still only shown through a view you built and granted. Set your system to anything but public and every profile and link on it returns "not found", with your views left intact for when you turn it back.

Per-member guards

A member can be marked never shareable, meaning they can't appear in any view under any circumstances, or fronting-private, meaning they can appear but their front status never does. Custom fronts are fronting-private by default, so a public page can't announce that you're asleep on an address anyone can poll. Archived members, and anyone queued for deletion, leave shared pages the moment you act.

Nothing gets published by accident

Making something visible waits out your System Safety grace period, takes re-authentication, and needs a one-time confirmation that you're 18 or older (recorded as a yes, with no date of birth and no documents). Populating a view from a group is a one-time pick, so adding someone to that group later never quietly publishes them. Backups round-trip your views but never your grants, so restoring one can't republish anything. Removing, revoking, and rotating are always immediate and never gated.

What visitors don't get

Shared pages ask search engines not to index them and browsers not to leak the address as a referrer. Images hosted elsewhere aren't loaded at all, so a third-party host never gets a log line for someone reading your profile. Only the name you chose to display is served, not the member's underlying name, so a scraper reading the page directly gets the same thing a person does. Share-link responses are cacheable only by the person who opened them, never by a CDN or proxy in between.

Preview as a visitor

See any view exactly as a stranger would, full screen, before anyone else can. It's not a mock-up: the page is built by the server from the same projection that serves the real one, so the two can't drift apart. It works on a view you've never published, which is the point of it. Opening a preview publishes nothing.

Built for reading

Who's fronting sits at the top and refreshes itself; members, groups, and relationships sit in tabs below, and a tab only appears if the view actually serves it. Big systems get a searchable member list rather than screens of scrolling. Optional member permalinks give each shown member an address of their own. Visitors get the page in their browser's light or dark preference, with a control to change it, stored in their browser and never sent to us.

Data integrity & safety

Revision history on a front

Revision history

Member bios and journal entries are versioned, with tier-aware retention caps so history doesn't grow unbounded.

Revision pinning

Pin specific revisions to protect them from automatic trim. Unpinning is optionally gated by re-auth and a grace period so a compromised session can't quietly nuke history.

System Safety configuration

System Safety

Optional grace period and re-authentication (password / TOTP) on destructive actions - member, journal, group, or message deletion, revision unpin, deleting a relationship type - and on profile visibility, so publishing anything waits out the same window. Per-category toggles so you can dial it in, and the settings page tells you what turning it on will actually cover before you save.

Front-history retention

An opt-in, per-system window that ages out closed fronting history past a chosen age - a privacy control, not a paid-tier limit, and off by default. Turning it on or tightening it is a deferred, re-auth-gated System Safety change with a cancellable countdown; loosening or disabling it applies immediately. Freshly imported history gets a 14-day grace so a restored archive is never abruptly deleted, and the setting is deliberately excluded from your export so restoring a backup can't re-arm a deletion policy.

Field-level encryption

Member names, descriptions, journal titles and bodies, notes, revision history, and fronting status notes are all encrypted at rest with XChaCha20-Poly1305 via libsodium. Email and TOTP secrets too. Encrypted values are being moved to a format that cryptographically binds each one to the exact record and column it belongs to, so nobody with raw database access can silently relocate a value from one record into another.

Import & export

SimplyPlural and PluralKit import

SimplyPlural import

Import your SP export with granular control. Pick specific members, toggle front history, choose what comes across.

PluralKit import

Import a PluralKit export file or pull live from PluralKit using your pk;token. PK switch log is converted to Sheaf front intervals.

More importers

Tupperbox, PluralSpace, Prism (encrypted .prism exports), and Ampersand (systems become groups, with nested subsystems preserved). All deduplicate against your existing roster on re-import, and anything a source has that Sheaf can't model is listed on the import's detail page rather than silently dropped.

Import preview

Every import shows you what it's about to do before you commit: what's coming across, what will be deduplicated, what exceeds Sheaf's field limits and will be shortened, and what caps apply. Cancel and fix the source file, or continue knowingly - no discovering the truncation afterwards.

PluralPort support

Founding project for the PluralPort data standard (formerly OpenPlural), shipping import and export for v0.1 today. Export as a single JSON document or a .pluralport.zip bundle carrying the image bytes; import either shape, and files written against the old name still import unchanged. Everything Sheaf has that the draft spec doesn't model yet is preserved losslessly under a namespaced extensions key, so a Sheaf → PluralPort → Sheaf round-trip is complete, and data from other apps that Sheaf can't model is preserved on import and re-emitted on the next export rather than being dropped on the floor.

Uploads and export settings

Data export

Sync JSON export (Article 20 portability), async zip with image bytes for full backups, and a separate Article 15 endpoint covering everything we know about your account. The full export imports back as-is, image bytes included, so backup-and-restore and instance-to-instance migration both keep everything. Your data stays yours.

Front-history export

Export just your fronting history, in whichever format suits what you want to do with it: CSV (one row per front, with duration and co-fronters, for spreadsheets), JSON (structured and self-describing), or ICS (an iCalendar file, one event per front, to see your history on a calendar timeline).

Account & access

Two-factor auth

Optional TOTP with recovery codes. Secrets encrypted at rest with libsodium.

Sessions and scoped API keys

Scoped API keys

Named sk_… keys with granular scopes for scripts and integrations. Plaintext shown once, never again.

Account activity log

A curated record of the consequential and automated things that happen to your account, so nothing happens silently: password, email, two-factor, API key, session and trusted-device changes, scheduled deletions, and system actions like an import completing or an export becoming ready. Carries no member content and no IP, ages out after a generous window, and is included in the Article 15 access export.

Account deletion

Self-service with a configurable grace period. Real deletion, not a "disabled" flag.

Clients

Web app

React 19 SPA. Dark Reader compatible.

Display preferences

Date format and display timezone are per-account preferences that sync across your devices, with a per-device override so a machine in another zone can pin its own without moving the account default. Timezone defaults to automatic (each device follows its own clock), and absolute times carry the zone they're shown in, so there's no ambiguity.

Themes

15+ themes, each with light and dark variants: Purple, Classic, OLED, Material You (Android, picks up your device wallpaper), Mint, Ocean, Sepia, Crimson, Goldenrod, Plural, Pride, Trans, Non-binary, Bi, Pan, and Asexual. Available on web, Android, and iOS. See them →

Android

Get it on Google Play

Or as a sideloadable APK from GitHub Releases. Wear OS companion and complication included.

iOS

Download on the App Store

watchOS companion and complication included.

Home Assistant

An official integration surfaces your system as Home Assistant entities: a binary sensor per member, current-fronter and count sensors, a quick-switch select, optional per-member switches, an event you can trigger automations on, and services to change the front. Updates by polling, live stream, or webhook. You control exactly which members it can see. Early days, but working - see the repo for current state.

Open API

Everything under /v1/. Full OpenAPI spec and interactive docs. Build your own client - CLIENT_DESIGN.md covers auth flows, scopes, and session management.

Instance admin

Admin dashboard

User management, invite codes, storage audit, background job monitoring, server announcements (with inline links and a settable expiry), optional step-up auth on sensitive operations.

Abuse investigation tools

An append-only security-event log records the auth funnel with originating IP, so an operator can answer "is this IP credential-stuffing?" and "what happened on this account before the takeover report". Exact-IP and CIDR lookup, a top-failing-IPs view ranked by distinct accounts targeted, and a per-account auth timeline. Targeted lookups require a stated reason and write an admin audit row; rows age out after a bounded retention window, since IP is personal data.

Destructive-job kill switch

One operator flag halts every data-deleting background job at once - the retention sweeps, orphaned-file and unverified-account cleanup, activity-log aging, and the jobs carrying out user-requested deletions - for use while investigating a retention issue, instead of zeroing each job's interval one at a time. Manual triggers refuse a destructive job while the freeze is on unless explicitly overridden, and the override is audited.

Sharing controls

Public profiles and share links are off unless you switch them on for your instance. Operators can take one system's shared pages down in response to an abuse report and hold them down, so a link that raced the takedown can't keep serving and the owner can't immediately republish; only an operator lifts the hold, and it's recorded in the admin audit log. An optional abuse and DMCA contact of your own wording appears in the footer of shared pages.

Registration modes

Open, approval-required, invite-only, or closed. Configurable email verification flow.

File storage

Avatar and file uploads. Filesystem or S3-compatible backends (S3, Backblaze B2, R2, MinIO). Signed URLs with presign support for hotlink protection.

Verifiable builds

Signed Docker images (sigstore/cosign keyless OIDC, recorded in Rekor's public transparency log) and a verifiable frontend bundle with Subresource Integrity hashes. Anyone can confirm a running instance corresponds to the public source. See VERIFYING.md.

Tech stack

Backend. Python 3.12+, FastAPI, SQLAlchemy 2.0 (async), PostgreSQL 16, Redis, Alembic migrations. Field-level encryption with XChaCha20-Poly1305 via libsodium.

Frontend. React 19, TypeScript, Vite, Tailwind CSS v4, shadcn/ui.

API. Everything under /v1/. OpenAPI spec at /v1/openapi.json. Interactive docs at /v1/docs. Dual auth: JWT bearer tokens (15m access + 30d refresh) or scoped API keys. Building a client? See CLIENT_DESIGN.md.