Papermark is an open-source, self-hostable document-sharing platform (a DocSend alternative) built on Next.js and PostgreSQL, with secure links, custom domains, and document analytics.
- Language
- TypeScript
- Database
- PostgreSQL
- Hosting
- Vercel
- Running for
- 1 year 6 months
- Team
- Solo
Why this architecture
Splitting enterprise-only code into an ee/ directory keeps the open-source core unambiguous, while pluggable S3-or-Vercel-Blob storage, a separate Tinybird analytics pipeline, and Trigger.dev-managed background jobs let the same Next.js app scale from a Vercel deployment to a self-hosted one.
Tech stack by layer
13 technologies · audited Sep 17, 2026- Next.jsRuns both the legacy Pages Router (pages/) and the App Router (app/(auth), app/(ee)) side by side.
- ReactComponent layer for both routers, on React 18.
- Tailwind CSSUtility CSS with tailwindcss-animate and forms plugins.
- Radix UIHeadless primitives (@radix-ui/react-dialog, -dropdown-menu, -tabs, and more) underlying the component library.
- Next.jsAPI routes under pages/api and app/api implement the backend; vercel.json tunes memory for two of them.
- NextAuth.jsHandles authentication, including a passkeys provider and SAML SSO via @boxyhq/saml-jackson.
- Trigger.devRuns background jobs, including Python-based tasks via the @trigger.dev/python package.
- mupdfServer-side PDF rendering and annotation; two API routes get 2048MB Vercel function memory for it.
- PostgreSQLPrimary relational database, per the README and the postgresql topic on the repo.
- PrismaORM for the Postgres schema; the schema is split into multiple files under prisma/schema instead of one file.
- S3One of two supported blob storage backends via @aws-sdk/client-s3 and @aws-sdk/lib-storage.
- Vercel BlobAlternative blob storage backend via @vercel/blob, selectable instead of S3.
- Redis@upstash/redis and @upstash/ratelimit back caching and API rate limiting.
- Vercelvercel.json configures per-function memory; vercel-build script runs prisma migrate deploy before next build.
- TinybirdSeparate real-time analytics service; the Pipfile pins tinybird-cli to push datasources from lib/tinybird.
- StripeBilling integration, used in both the core app and the ee/stripe enterprise module.
- OpenAI APIThe AI SDK (ai, @ai-sdk/openai, @ai-sdk/google-vertex) and openai package power AI features such as document Q&A.
- Model Context Protocol@modelcontextprotocol/sdk is a direct dependency, exposing or consuming MCP tools.
Papermark architecture diagram
Open SVGDiagram as text
- Dashboard (Next.js · Radix) → Next.js app (Pages + App Router · NextAuth): HTTPS
- Document viewers (shared links) → Next.js app (Pages + App Router · NextAuth): view documents
- Next.js app (Pages + App Router · NextAuth) → PostgreSQL (Prisma): Prisma
- Next.js app (Pages + App Router · NextAuth) → Blob storage (S3 or Vercel Blob): files
- Next.js app (Pages + App Router · NextAuth) → Upstash Redis (cache · rate limits): rate limit
- Next.js app (Pages + App Router · NextAuth) → Trigger.dev jobs (Node · Python tasks): trigger
- Next.js app (Pages + App Router · NextAuth) → PDF processing (mupdf): render
- Next.js app (Pages + App Router · NextAuth) → Tinybird (view analytics): view events
Key architectural decisions
6 decisions- 01
Pages Router and App Router coexist in the same Next.js app
The tree has both a pages/ directory (pages/_app.tsx, pages/api, pages/dashboard.tsx) and an app/ directory with route groups app/(auth) and app/(ee), so newer features migrate to the App Router while older routes stay on the Pages Router rather than a big-bang rewrite.
- 02
Enterprise features live in an isolated ee/ directory with its own license
ee/LICENSE.md, ee/features, ee/limits and ee/stripe separate paid-tier code (billing limits, Stripe integration) from the core app, an open-core structure that keeps the base product’s license unambiguous.
- 03
Blob storage is pluggable between S3 and Vercel Blob
package.json depends on both @aws-sdk/client-s3/@aws-sdk/lib-storage and @vercel/blob, and the README lists "AWS S3 or Vercel Blob" as the two supported options, so deployments choose a backend via environment configuration instead of a hardcoded provider.
- 04
Document analytics run through Tinybird, not the Postgres database
A dedicated Pipfile pins tinybird-cli and lib/tinybird holds datasource/endpoint definitions pushed with
tb push, and the README’s "Tinybird Instructions" section documents this as a separate setup step from the Prisma/Postgres database, keeping high-volume view-event analytics off the primary OLTP store. - 05
Prisma schema is split into multiple files, not one schema.prisma
package.json sets "prisma": { "schema": "prisma/schema" }, pointing Prisma at a directory (prisma/schema) rather than the conventional single schema.prisma file, which lets large models be organized across several files.
- 06
Background and Python jobs both run through Trigger.dev v4
package.json depends on @trigger.dev/sdk, @trigger.dev/react-hooks, and @trigger.dev/python together with a trigger.config.ts at the repo root, so both TypeScript and Python background tasks are scheduled through one job platform instead of a custom queue.
How Papermark is built
How Papermark is structured
Papermark is a single Next.js application at the repository root, mixing the legacy Pages Router (pages/, including pages/api and pages/dashboard.tsx) with the newer App Router (app/(auth), app/(ee), app/api), so different parts of the product live on different routing systems rather than a full rewrite. Shared logic sits in lib/ (lib/prisma.ts, lib/auth, lib/analytics, lib/billing, lib/dataroom) and components/ mirrors the same feature areas (components/datarooms, components/documents, components/billing). Paid-tier functionality is isolated under ee/ (ee/features, ee/limits, ee/stripe) with its own ee/LICENSE.md, separating the open-source core from enterprise code at the directory level.
Frontend
The UI runs on React 18 with Tailwind CSS 3, using tailwindcss-animate and @tailwindcss/forms as plugins, and a component layer built on Radix UI primitives (@radix-ui/react-dialog, -dropdown-menu, -tabs, -tooltip, and more, per package.json). Document viewing and interaction combine react-pdf, pdf-lib, and mupdf on the client and server, @tiptap/react for rich-text agreement editing, notion-client/notion-utils/react-notion-x for embedding Notion pages, and @dnd-kit/core for drag-and-drop reordering in folders and datarooms. Internationalization runs through i18next/react-i18next with translations under locales/ for German, Spanish, French, Italian, Japanese, Dutch, Brazilian Portuguese, and English.
Backend & APIs
API routes live in both pages/api and app/api, and vercel.json sets memory: 2048 specifically for pages/api/mupdf/convert-page.ts and pages/api/mupdf/annotate-document.ts, confirming those PDF-processing endpoints run as Vercel serverless functions with elevated memory. Authentication runs through NextAuth.js (next-auth), extended with @teamhanko/passkeys-next-auth-provider for WebAuthn passkeys and @boxyhq/saml-jackson for enterprise SAML SSO, plus a standalone oidc-provider dependency for exposing an OIDC identity provider. AI-assisted features are built on Vercel's ai SDK with @ai-sdk/openai and @ai-sdk/google-vertex providers, the openai SDK directly, and @modelcontextprotocol/sdk for MCP integration.
Data & persistence
PostgreSQL is the primary datastore, accessed through Prisma (@prisma/client, prisma devDependency) with "prisma": { "schema": "prisma/schema" } in package.json pointing at a directory of schema files rather than a single schema.prisma, and prisma/migrations tracking the history. File storage is pluggable between two backends: @aws-sdk/client-s3/@aws-sdk/lib-storage/@aws-sdk/cloudfront-signer for S3, or @vercel/blob for Vercel's native blob storage, matching the README's "Blob storage (currently AWS S3 or Vercel Blob)" line. Caching and rate limiting go through Upstash (@upstash/redis, @upstash/ratelimit, @upstash/qstash for queued jobs), and @vercel/edge-config provides fast-read configuration data at the edge.
Build, test & deploy
The vercel-build script runs prisma migrate deploy && next build, applying pending database migrations before every production build, which only makes sense in a deployment pipeline that already has a DATABASE_URL configured. Background and scheduled work runs through Trigger.dev v4 (@trigger.dev/sdk, @trigger.dev/react-hooks, @trigger.dev/build, and a root trigger.config.ts), which notably also includes @trigger.dev/python as a dependency, meaning some tasks execute as Python jobs orchestrated by the same platform as the TypeScript ones. The only GitHub Actions workflow in the repository, cla.yml, enforces a contributor license agreement rather than running tests or builds, so CI-as-such is expected to run on Vercel's own build pipeline (triggered by vercel-build) instead of GitHub Actions.
Self-hosting notes
Papermark can run outside Vercel: the README's quickstart is git clone, npm install, cp .env.example .env, npm run dev:prisma, npm run dev, requiring only a PostgreSQL database, an S3-or-Vercel-Blob-compatible storage bucket, and a Resend account for email. The separate Tinybird analytics pipeline needs its own setup (installing the Tinybird CLI, authenticating, and running tb push datasources/* from lib/tinybird), and a TINYBIRD_TOKEN environment variable, so self-hosters who want document view analytics take on that extra service beyond Postgres.
What to copy (and what not to)
Isolating paid features under an ee/ directory with its own license file is a clean, low-friction way to run an open-core business without obscuring what is actually open source, and is directly reusable by similar projects. Making blob storage pluggable between a generic S3 client and a platform-specific one (Vercel Blob) is a good pattern when the primary deployment target and the self-hosting target have different native storage options. Less reusable without matching scale: pulling in a dedicated analytics service (Tinybird) with its own CLI and push workflow adds real self-hosting friction, and a smaller project might prefer to start with database-backed analytics before introducing a second data platform.
Sources & repo audit
- Audited
- Sep 17, 2026
- Commit
- ed19717
- License
- NOASSERTION
- Root package.json (Next.js, Prisma, NextAuth, Trigger.dev, AI SDK dependencies)
- vercel.json (per-function memory for mupdf PDF routes)
- Pipfile (tinybird-cli for the analytics pipeline)
Independent analysis of repository at github.com/mfts/papermark. Spotted an inaccuracy? Use the claim form to request a correction.
Show this stack on your README or website
[](https://stackitfast.com/project/papermark)Paste it into your README. It shows your current stack and links to this page.
Scaffold it with your agent
Paste this prompt into Claude Code, Cursor, Windsurf or AGY to start a project with Papermark's architecture.
- 1Copy the promptThe full markdown spec, with every layer and decision.
- 2Open your AI toolClaude Code, Cursor, Windsurf or Copilot, in a new repo.
- 3Paste and scaffoldUse it as the first instruction; review before you ship.
# MISSION: Scaffold "Papermark" Production Architecture
You are an expert Senior Staff Software Architect and Full-Stack Engineer. Your mission is to scaffold and implement a production-grade, highly reliable, and modular codebase following the proven architecture of **Papermark**.
---
## 1. PROJECT SPECIFICATIONS & BENCHMARK
- **Reference Architecture**: Papermark
- **What It Does**: Papermark is an open-source, self-hostable document-sharing platform (a DocSend alternative) built on Next.js and PostgreSQL, with secure links, custom domains, and document analytics.
- **Domain & Category**: Document Analytics SaaS
- **Production Scale**: Solo
- **Development Mode**: AI-AGENT
- **Architectural Rationale**: Splitting enterprise-only code into an ee/ directory keeps the open-source core unambiguous, while pluggable S3-or-Vercel-Blob storage, a separate Tinybird analytics pipeline, and Trigger.dev-managed background jobs let the same Next.js app scale from a Vercel deployment to a self-hosted one.
- **Live Website Reference**: https://papermark.io
- **Source Repository**: https://github.com/mfts/papermark
---
## 2. PRODUCTION TECH STACK
- **Full Stack Array**: TypeScript, Next.js, React, Prisma, PostgreSQL, Tailwind CSS, NextAuth.js, Stripe, Resend, Vercel, S3, Zod, Tinybird
- **Primary Language(s)**: TypeScript, JavaScript, CSS
- **License of the reference repo**: NOASSERTION
- **Frontend**: Next.js — Runs both the legacy Pages Router (pages/) and the App Router (app/(auth), app/(ee)) side by side.; React — Component layer for both routers, on React 18.; Tailwind CSS — Utility CSS with tailwindcss-animate and forms plugins.; Radix UI — Headless primitives (@radix-ui/react-dialog, -dropdown-menu, -tabs, and more) underlying the component library.
- **Backend & APIs**: Next.js — API routes under pages/api and app/api implement the backend; vercel.json tunes memory for two of them.; NextAuth.js — Handles authentication, including a passkeys provider and SAML SSO via @boxyhq/saml-jackson.; Trigger.dev — Runs background jobs, including Python-based tasks via the @trigger.dev/python package.; mupdf — Server-side PDF rendering and annotation; two API routes get 2048MB Vercel function memory for it.
- **Data & persistence**: PostgreSQL — Primary relational database, per the README and the postgresql topic on the repo.; Prisma — ORM for the Postgres schema; the schema is split into multiple files under prisma/schema instead of one file.; S3 — One of two supported blob storage backends via @aws-sdk/client-s3 and @aws-sdk/lib-storage.; Vercel Blob — Alternative blob storage backend via @vercel/blob, selectable instead of S3.; Redis — @upstash/redis and @upstash/ratelimit back caching and API rate limiting.
- **Infrastructure & deploy**: Vercel — vercel.json configures per-function memory; vercel-build script runs prisma migrate deploy before next build.; Tinybird — Separate real-time analytics service; the Pipfile pins tinybird-cli to push datasources from lib/tinybird.; Stripe — Billing integration, used in both the core app and the ee/stripe enterprise module.
- **Tooling, testing & ops**: OpenAI API — The AI SDK (ai, @ai-sdk/openai, @ai-sdk/google-vertex) and openai package power AI features such as document Q&A.; Model Context Protocol — @modelcontextprotocol/sdk is a direct dependency, exposing or consuming MCP tools.
---
## 3. KEY ARCHITECTURAL DECISIONS (audited from https://github.com/mfts/papermark @ ed19717)
1. **Pages Router and App Router coexist in the same Next.js app**: The tree has both a pages/ directory (pages/_app.tsx, pages/api, pages/dashboard.tsx) and an app/ directory with route groups app/(auth) and app/(ee), so newer features migrate to the App Router while older routes stay on the Pages Router rather than a big-bang rewrite.
2. **Enterprise features live in an isolated ee/ directory with its own license**: ee/LICENSE.md, ee/features, ee/limits and ee/stripe separate paid-tier code (billing limits, Stripe integration) from the core app, an open-core structure that keeps the base product’s license unambiguous.
3. **Blob storage is pluggable between S3 and Vercel Blob**: package.json depends on both @aws-sdk/client-s3/@aws-sdk/lib-storage and @vercel/blob, and the README lists "AWS S3 or Vercel Blob" as the two supported options, so deployments choose a backend via environment configuration instead of a hardcoded provider.
4. **Document analytics run through Tinybird, not the Postgres database**: A dedicated Pipfile pins tinybird-cli and lib/tinybird holds datasource/endpoint definitions pushed with `tb push`, and the README’s "Tinybird Instructions" section documents this as a separate setup step from the Prisma/Postgres database, keeping high-volume view-event analytics off the primary OLTP store.
5. **Prisma schema is split into multiple files, not one schema.prisma**: package.json sets "prisma": { "schema": "prisma/schema" }, pointing Prisma at a directory (prisma/schema) rather than the conventional single schema.prisma file, which lets large models be organized across several files.
6. **Background and Python jobs both run through Trigger.dev v4**: package.json depends on @trigger.dev/sdk, @trigger.dev/react-hooks, and @trigger.dev/python together with a trigger.config.ts at the repo root, so both TypeScript and Python background tasks are scheduled through one job platform instead of a custom queue.
---
## 4. NON-NEGOTIABLE ARCHITECTURAL GUARDRAILS
1. **Monorepo & Modular Separation**:
- Structure as a Turborepo monorepo with strict package boundaries:
- `apps/web`: Application UI, routing, layouts, and server endpoints.
- `packages/ui`: Shared design tokens, CSS variables, and Radix UI primitive components.
- `packages/db`: Database schemas, client singleton, declarative migrations, and seed scripts.
- `packages/config`: Shared TypeScript, ESLint, and build configurations.
2. **Strict Type Safety & Zero `any` Policy**:
- Enable `strict: true`, `noImplicitAny: true`, and `strictNullChecks: true`.
- Validate ALL external inputs, API request bodies, and query parameters with **Zod** schemas before execution.
3. **Frontend & Rendering Guidelines**:
- Utilize Next.js 15 App Router: default to React Server Components (RSC) for data fetching. Handle mutations through Server Actions with Zod validation. Keep 'use client' directives restricted strictly to interactive leaf components.
4. **Design System & Aesthetics**:
- Keep every color, radius, shadow and font in a single token file (CSS variables) and consume tokens everywhere; never hardcode hex values in components.
- Prefer crisp 1px borders and one subtle shadow scale over blurry default shadows. Pair one sans-serif for body/headings with one monospace for tags, badges, metrics, and code.
5. **Data Layer & Reliability**:
- Write declarative schema definitions with foreign keys, composite indexes on queried filters, and automated timestamp triggers.
- Use connection pooling and prepared statements for serverless database execution.
---
## 5. STEP-BY-STEP SCAFFOLDING ROADMAP
- **Phase 1: Workspace & Root Config**: Initialize package manager, monorepo configuration (`turbo.json`, `tsconfig.base.json`, `package.json`).
- **Phase 2: Database Schema & Client**: Set up the data layer (PostgreSQL, Prisma, S3, Vercel Blob, Redis): client, connection pool, models, and migration scripts.
- **Phase 3: Design Tokens & UI Primitives**: Build accessible `Button`, `Input`, `Card`, `Badge`, and layout wrappers inside `packages/ui`.
- **Phase 4: Core Application Routes & Handlers**: Implement primary authentication, user session handling, and application routes.
- **Phase 5: Quality Assurance & Build Verification**: Run `tsc --noEmit`, ESLint, Prettier, and smoke test suites to ensure zero compilation or runtime errors.
---
## 6. EXECUTION INSTRUCTIONS
1. Review all specifications, architectural guardrails, and stack choices above.
2. Present the full monorepo directory tree structure.
3. Systematically generate the complete, production-ready codebase according to the 5-phase roadmap above — starting with the root workspace setup, followed by the database schema, UI design system package, and full-stack application routes until the repository is fully scaffolded and ready to run.Frequently asked about Papermark
What is Papermark built with?
Next.js (mixing the Pages Router and App Router) with TypeScript, Prisma over PostgreSQL, Tailwind CSS, NextAuth.js for auth, and Trigger.dev for background jobs.
What database does Papermark use?
PostgreSQL, accessed through Prisma with a multi-file schema under prisma/schema instead of a single schema.prisma file.
Can Papermark be self-hosted, or is it Vercel-only?
It can be self-hosted (the README documents cloning the repo, running npm install and npm run dev), but vercel.json and a vercel-build script that runs Prisma migrations show it is built with Vercel deployment as the primary target.
How does Papermark track document view analytics?
Through Tinybird, a separate real-time analytics service configured via lib/tinybird and pushed with the Tinybird CLI, kept apart from the Postgres application database.
Does Papermark have an enterprise or paid edition?
Yes. The ee/ directory holds enterprise-only features (billing limits, Stripe integration) under a separate license file, ee/LICENSE.md, distinct from the core app.
One email a month: new deep dives and stack trends
New source-audited architectures, head-to-head comparisons and the monthly stack report. No spam, unsubscribe anytime.
Similar architectures
- TaxonomyAI-agentOpen-Source SaaS Template · 1-5 peopleShares Next.js · React · TypeScript
- DubHybridLink & Analytics SaaS · 2-5 peopleShares Next.js · TypeScript · Prisma
- UmamiHybridWeb Analytics · SoloShares TypeScript · Next.js · React
- TypebotAI-agentConversational Form Builder · SoloShares TypeScript · Next.js · React
- FormbricksAI-agentSurvey & Feedback SaaS · 2-5 peopleShares Next.js · TypeScript · Prisma
- GrafanaClassicMonitoring & Observability Dashboard · 20+ peopleShares TypeScript · React · PostgreSQL