Folder-first publishing. Drop a workspace folder with an index.html, control access, and share a live URL.
Features · Stack · Getting started · Configuration · Layout · API
PublishOS is a self-hostable web app for managing sites as folders: create folders, upload static assets (HTML, CSS, images, archives), tune sharing and publishing (public, private, password), and track activity (stats, logs, notifications). It targets teams and individuals who want one clear workflow from files on disk to a URL—without a separate CMS for simple static sites.
App icons shipped with the Next.js app (used in the shell and for README display on GitHub):
| Asset | Path |
|---|---|
| Default app icon | app/icon.png |
| Apple touch | app/apple-icon.png |
| Favicon | app/favicon.ico |
The optional static marketing landing site lives in a local marketing/ directory in this workspace; that folder is not tracked in Git for this repository so it stays out of the remote.
Optional UI screenshots for this README: add images under docs/screenshots/ (for example home.png, folder-detail.png) and reference them with relative paths so they render on GitHub.
- Folder tree with breadcrumbs, nested folders, and a home workspace view.
- Files with upload (multipart), size limits, MIME handling, and collision-safe naming.
- ZIP extract into a folder where supported.
index.htmlawareness so folders can represent publishable sites.- Trash with restore and purge flows for files and folders.
- Duplicate files and folders.
- Download folder archives where implemented.
- Share drawer for access and publishing controls on folders.
- Publishing modes (e.g. off / public / password-gated) with validation.
- Slugs and hosting-oriented settings driven from the app.
- Command palette search (keyboard shortcut from the app shell) wired to a search API.
- Sort and filter state integrated with the UI (URL-backed where applicable).
- People views and user-centric actions (profile, roles where applicable).
- Teams and invitations APIs for workspace membership.
- Workspace roles (e.g. owner / admin / editor / viewer) affecting capabilities such as uploads.
- Stats and logs routes for usage and anomaly surfacing.
- Notifications (list, unread counts, mark read / clear).
- Live feed API for recent activity-style data.
- Auth.js (NextAuth) v5 with credentials sign-in and optional Google OAuth.
- JWT sessions with a secure
AUTH_SECRET. - Registration with optional signup domain allowlist (admin-configurable).
- Password reset and change password flows (email-dependent when SMTP is configured).
- Avatar upload for the signed-in user.
- Must-change-password gate surfaced in the shell when required.
- App settings: storage backend (local filesystem vs S3-compatible), email (SMTP), signup policy, API access toggles, and related operator controls.
- API tokens for programmatic access (create/revoke; used by versioned APIs when enabled).
POST /api/v1/sites— Bearer-token authenticated endpoint to create a new folder and push HTML or multi-file sites in one request (designed for tools and automation). Bypasses the session cookie proxy; enable and manage tokens from settings when available.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 App Router |
| Language | TypeScript (strict) |
| UI | React 19, Radix UI primitives, Tailwind CSS v4 (@import "tailwindcss" in styles/globals.css) plus design tokens and component-scoped class names |
| Auth | Auth.js v5 (next-auth@beta), @auth/drizzle-adapter |
| Database | SQLite via better-sqlite3, Drizzle ORM |
| Passwords | bcryptjs |
| Client data | TanStack Query for mutations/cache; Zustand for global UI |
| Object storage | Local dir under configurable root, or S3-compatible (R2, AWS, etc.) via app settings (see lib/storage/) |
| Validation | Zod |
- Node.js 20+ (LTS recommended)
- pnpm (the repo is set up for
pnpm; you can adapt fornpmoryarnif needed)
pnpm installCopy the template and edit locally (.env.local stays gitignored; .env.example is safe to commit):
cp .env.example .env.local
# Then set AUTH_SECRET — at least 16 characters, e.g.:
# echo "AUTH_SECRET=$(openssl rand -base64 32)" >> .env.localMinimum required values:
# Required — at least 16 characters; generate e.g. openssl rand -base64 32
AUTH_SECRET=your-long-random-secret-here
# Optional — defaults to publishos.db in the project root (gitignored)
# DATABASE_PATH=publishos.db
# Optional — canonical URL for Auth.js callbacks in production
# AUTH_URL=https://your-domain.exampleVercel: In Settings → Environment Variables, set AUTH_SECRET (16+ characters) and AUTH_URL (your deployment URL, e.g. https://your-project.vercel.app) for Preview and Production. The build can complete without them in some CI steps, but the running app needs a real AUTH_SECRET for Auth.js.
Optional OAuth (Google): if both are set, the login UI can offer Google sign-in.
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=Optional transactional email (Resend): for magic-link or email-dependent flows when wired in the app.
RESEND_API_KEY=
EMAIL_FROM=noreply@your-domain.exampleThe authoritative schema for parsed env vars is lib/env.ts.
pnpm drizzle-kit pushFor migration-based workflows:
pnpm drizzle-kit generate # emit SQL under drizzle/
pnpm drizzle-kit migrate # apply migrationspnpm devOpen http://localhost:3000. Unauthenticated visitors are sent to /login. Use Create a workspace (register) on /register to bootstrap the first user if sign-up is open.
pnpm build
pnpm startDATABASE_PATH— path to the SQLite file. Defaultpublishos.dbin the repo root.- Database artifacts (
*.db, journals, WAL) are gitignored.
Storage is not configured only through environment variables: the active backend is read from app_settings (see lib/data/settings.ts).
- Local (default): files live under a configurable root (default relative
storage/, gitignored). - S3-compatible: set bucket, region, endpoint, access key, secret, and optional public URL in Settings in the app. If S3 fields are incomplete, the app falls back to local storage rather than failing writes.
- Edge-safe auth config:
lib/auth.config.ts— imported by the Next.js proxy. - Full auth (DB, credentials):
lib/auth.ts. - Proxy / middleware:
proxy.ts— protects app routes; allows/api/auth/*,/api/v1/*(Bearer tokens), static icon routes, and/brand/*without a session.
app/
(app)/ # Authenticated app: home, folders, stats, logs, people, settings, profile, trash, …
(auth)/ # login, register, password reset, …
api/ # Route handlers: folders, files, search, teams, invitations, settings, v1, …
components/ # Shell, folder views, modals, share drawer, search palette, …
lib/
auth.ts / auth.config.ts
db/ # Drizzle client + schema
data/ # Server-only data access used by RSCs and APIs
storage/ # Local + S3 backends
env.ts # Validated environment
proxy.ts # Auth gate (Next.js 16 “proxy” convention)
styles/globals.css # Design tokens and global styles
# marketing/ # optional local-only static site (gitignored in this repo)
drizzle/ # Generated migrations (when used)
| Area | Auth | Examples |
|---|---|---|
| Session (browser) | Cookie session via Auth.js | /api/folders, /api/files, /api/search, … |
| NextAuth | Same | /api/auth/* |
| Versioned HTTP API | Authorization: Bearer <token> |
POST /api/v1/sites — create folder + files from JSON/HTML |
API token issuance and API access toggles are tied to settings and account UI when enabled. See lib/api-auth.ts for Bearer resolution.
| Command | Description |
|---|---|
pnpm dev |
Development server |
pnpm build |
Production build |
pnpm start |
Serve production build |
pnpm lint |
ESLint |
pnpm test |
Vitest unit tests |
pnpm drizzle-kit push |
Push schema to DB (dev-friendly) |
pnpm drizzle-kit generate |
Create migration files |
pnpm drizzle-kit migrate |
Run migrations |
pnpm testThe repo lists @playwright/test as a dev dependency; add Playwright specs and config as needed for end-to-end tests.
- Fork and clone the repository.
- Create a branch for your change.
- Run
pnpm lintandpnpm testbefore opening a pull request. - Keep diffs focused; match existing patterns in
lib/dataandapp/api.
- Never commit
.env.local, database files, or thestorage/directory. - Rotate
AUTH_SECRETif it is ever leaked. - In production, set
AUTH_URLto your public origin so OAuth and email links resolve correctly. - Treat API tokens like passwords; store them in a secret manager, not in client-side code.
PublishOS is released under the MIT License.
Copyright (c) 2026 Design Value. See LICENSE for the full text.
Built with Next.js, Drizzle, and Auth.js.