Family-first social space focused on private sharing and memory preservation.
Fircle is currently optimized for shipping a focused family-first product quickly:
- Single-family mode first (faster path to usable product)
- Invite-only registration
- Family member profiles that can exist before account creation (unclaimed members)
- Account claiming flow for later ownership
- Photo/video posts with member tagging
- Next.js (App Router)
- TypeScript
- tRPC
- Prisma
- NextAuth.js
- Tailwind CSS
- shadcn/ui
- PostgreSQL
For a full production and local self-hosting walkthrough (PaaS-first), see:
Related storage guides:
pnpm installCreate a .env file in the project root.
Required variables:
DATABASE_URL(PostgreSQL connection string)AUTH_SECRET(required in production, optional in development)NODE_ENV(development,test, orproduction)STORAGE_DRIVER(r2)
Storage configuration depends on deployment mode:
- Cloud mode (
SELF_HOSTED=false): configure storage in Settings > Integrations. EnvironmentR2_*values are ignored. - Self-hosted mode (
SELF_HOSTED=true): you can keep usingR2_*env values or migrate to owner-managed storage in Settings > Integrations.
If you want to use environment-backed storage in self-hosted mode, provide:
R2_ACCOUNT_ID(Cloudflare account id)R2_BUCKET(R2 bucket name)R2_ACCESS_KEY_ID(R2 API access key id)R2_SECRET_ACCESS_KEY(R2 API secret access key)R2_PUBLIC_BASE_URL(public read URL base for uploaded objects)
Optional transactional email variables (required only when EMAIL_DRIVER=zeptomail):
EMAIL_DRIVER(zeptomail)EMAIL_FROM_ADDRESS(sender email address)EMAIL_FROM_NAME(sender display name)ZEPTOMAIL_API_KEY(ZeptoMail API key)ZEPTOMAIL_ACCOUNT_ID(ZeptoMail account id)ZEPTOMAIL_API_BASE_URL(optional override, defaults tohttps://api.zeptomail.com)
Web push variables:
NEXT_PUBLIC_VAPID_PUBLIC_KEY(browser-safe VAPID public key)VAPID_PRIVATE_KEY(server-only VAPID private key)VAPID_SUBJECT(mailto:you@example.comor anhttps://URL)
Push env behavior:
- In
production, all three VAPID variables are required. - In
developmentandtest, VAPID variables are optional unless any one of them is set; then all three are required.
Generate VAPID keys:
pnpm dlx web-push generate-vapid-keysCopy the generated values into .env as NEXT_PUBLIC_VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY, then set VAPID_SUBJECT to a valid mailto: or https:// value.
Example:
DATABASE_URL="postgresql://postgres:password@localhost:5432/fircle"
AUTH_SECRET="dev-secret"
NODE_ENV="development"
STORAGE_DRIVER="r2"
# Cloud mode: configure storage in the app settings instead of env.
# Self-hosted mode: uncomment these if you want env-backed storage fallback.
R2_ACCOUNT_ID="your-cloudflare-account-id"
R2_BUCKET="fircle-media"
R2_ACCESS_KEY_ID="your-r2-access-key-id"
R2_SECRET_ACCESS_KEY="your-r2-secret-access-key"
R2_PUBLIC_BASE_URL="https://media.example.com"
# Optional: transactional email provider
# Leave EMAIL_DRIVER unset to run without outbound email.
EMAIL_DRIVER="zeptomail"
EMAIL_FROM_ADDRESS="noreply@example.com"
EMAIL_FROM_NAME="Fircle"
ZEPTOMAIL_API_KEY="your-zeptomail-api-key"
ZEPTOMAIL_ACCOUNT_ID="your-zeptomail-account-id"
# Optional override
# ZEPTOMAIL_API_BASE_URL="https://api.zeptomail.com"
# Optional in development/test, required in production for web push
NEXT_PUBLIC_VAPID_PUBLIC_KEY="your-vapid-public-key"
VAPID_PRIVATE_KEY="your-vapid-private-key"
VAPID_SUBJECT="mailto:you@example.com"Option A (Linux/macOS, or Windows via WSL):
./start-database.shOption B: use your own PostgreSQL instance and set DATABASE_URL accordingly.
Direct uploads from the browser to signed R2 URLs require bucket CORS rules. If uploads fail with a network/CORS-style error, configure the R2 bucket CORS to allow:
AllowedOrigins: your app origin (for local dev,http://localhost:3000)AllowedMethods:PUT,GET,HEADAllowedHeaders:content-typeExposeHeaders:etag
You can add additional origins for staging/production as needed.
Fircle supports owner-managed storage credentials through Settings > Integrations.
- In self-hosted mode, storage can still fall back to
R2_*environment variables if no owner-managed credential exists. - In cloud mode, storage must be configured per family in the app settings. Any
R2_*environment values are ignored. - The owner-facing setup flow includes a credential test step before saving.
See the rollout guides:
For local development:
pnpm db:generateAlternative quick sync:
pnpm db:pushpnpm devOpen http://localhost:3000.
For a fresh self-hosted instance with no family data yet:
- Open
/auth/setupin your browser. - Enter your family name, owner name, owner email, and password.
- Submit once to create the first family and owner account.
This setup route is only available before the first family is created.
The setup wizard runs active readiness probes before allowing first-family bootstrap:
- Database: fresh Prisma connect +
SELECT 1 - Object storage: R2
HeadBucketwith configured credentials - Web push: VAPID presence and runtime key compatibility validation
- Transactional email: ZeptoMail auth probe when
EMAIL_DRIVER=zeptomail
If you change .env values while the dev server is running, restart pnpm dev before re-checking readiness so updated environment values are applied.
If you are running a cloud deployment and storage has not been configured yet, media uploads will be blocked until an owner adds storage credentials in Settings > Integrations.
If you are self-hosting, you can continue using R2_* env variables or migrate to owner-managed storage at your own pace.
Fircle resolves tenant context exclusively through explicit Domain rows.
There is no subdomain parsing fallback and no singleton-instance fallback.
- Platform-style domains are mapped explicitly (for example,
family-slug.fircle.app) - Custom domains are mapped explicitly (for example,
family.example.com) - Self-host root domains are mapped explicitly (for example,
localhostorintranet.local) - Unmapped hosts always resolve to
tenant-not-found
When adding a custom domain from Settings > Domain, Fircle generates a stored verification token and exposes two supported proof methods. The server performs the proof check itself when you run verification; verification does not succeed from copying the token back into the app.
Create a DNS TXT record using the values shown in the UI:
- Name:
_fircle-verification.<your-domain> - Type:
TXT - Value:
fircle-verification=<verification-token>
The backend verifies ownership by querying _fircle-verification.<your-domain> and looking for an exact TXT value match.
Serve the raw verification token at this endpoint:
- Method:
GET - URL:
https://<your-domain>/.well-known/fircle-verification - Response: plain-text body containing exactly
<verification-token>
The backend follows the HTTPS well-known URL above and compares the trimmed response body to the stored token.
- DNS changes can take time to propagate depending on your DNS provider and TTL.
- HTTP checks depend on the public domain already routing to the app or proxy that serves the well-known file.
- Verification performs bounded retries and returns actionable states for pending propagation, invalid proof, unreachable targets, or timeouts.
Verification proof not found yet: the TXT record is not visible yet, or the HTTP endpoint still returns404.Verification proof is present but does not match: the record or HTTP body is reachable but the token value is wrong.Verification target could not be reached: DNS lookup or HTTPS fetch failed for network or upstream availability reasons.Verification timed out: the verification check exceeded the configured timeout window; retry after the target becomes responsive.
In production mode, unverified domains are not allowed to resolve tenant data.
For self-host deployments, root/subdomain hosts must be represented in Domain.
If no family exists yet, self-host instances enter bootstrap flow (/auth/setup).
After setup, tenant resolution still depends on mapped domains.
- Account lookup is tenant-scoped (
familyId+ normalized email) - The same email can exist in different families without conflict
- Invite acceptance and claim flows only conflict on existing identities inside the same tenant
- Redirect callbacks are restricted to same-origin tenant hosts to prevent cross-tenant leakage
- Session cookies are host-scoped (no shared cookie domain), preventing cross-subdomain session bleed
Fircle uses a WebAPK-first PWA approach for Android Chrome installs, with iOS Safari Add to Home Screen baseline support.
- Start the app with
pnpm devand openhttp://localhost:3000. - Open DevTools > Application:
- verify
manifest.jsonis detected, - verify service worker
/sw.jsis active and controlling the page, - verify app icons and screenshots resolve.
- verify
- In notification settings, enable push and verify browser permission flow and subscription success.
- Trigger a notification-producing event and verify push click-through routing opens expected in-app context.
- Open Fircle in Chrome on Android.
- Install from browser menu (or install prompt).
- Verify launcher icon quality and app launch behavior.
- Validate push click-through while app is installed.
- Inspect
about://webapksin Chrome for generated WebAPK details.
- Open Fircle in Safari on iOS.
- Use Share > Add to Home Screen.
- Verify icon/title rendering and standalone launch behavior.
- Verify core navigation remains functional after install.
- If push subscription fails in VS Code integrated browser, test in regular Chrome or Edge.
- If service worker updates do not apply, reload once after registration update.
- If install visuals look stale, clear site data and reinstall.
pnpm dev- Start development serverpnpm build- Build for productionpnpm start- Start production serverpnpm preview- Build and start production locallypnpm lint- Run lint checkspnpm lint:fix- Auto-fix lint issues where possiblepnpm typecheck- Run TypeScript checkspnpm check- Run lint + typecheckpnpm format:check- Check formattingpnpm format:write- Write formatting changespnpm db:generate- Run Prisma migrate devpnpm db:migrate- Run Prisma migrate deploypnpm db:push- Push Prisma schema to DBpnpm db:studio- Open Prisma Studio
Core family onboarding and private sharing milestones are implemented.
Current product priorities:
- Production hardening for self-hosted deployments (ops, reliability, and recovery)
- Tenant and domain operations maturity (verification lifecycle and edge-case handling)
- Notification delivery robustness (push and email reliability under real-world conditions)
- Media lifecycle and storage hygiene (retention, cleanup automation, and operational safety)
- Product quality and UX polish across feed, onboarding, and settings surfaces
Contributions are welcome. Open an issue or pull request for bug fixes, improvements, and feature work aligned with the current roadmap.