An open source review platform,following the PRISMA (Preferred Reporting Items for Systematic reviews and Meta-Analyses) protocol, built with Next.js, React, and TypeScript.
Full information about the PRISMA guidelines can be found at https://www.prisma-statement.org/.
Prismatica is designed for teams running evidence reviews, offering a clear workflow path and and auditability:
- Structured PRISMA workflow from import to export
- Multi-user collaboration with role-aware controls
- Server-side state and signed HTTP-only sessions
- Full-text PDF handling with validation and provenance
- Configurable review thresholds and conflict handling
- Audit trail and project-level traceability
npm installnpm run dev -- --hostname 127.0.0.1 --port 3000Open http://127.0.0.1:3000.
npm run dev:https -- --hostname 127.0.0.1 --port 3000This uses Next.js experimental local HTTPS support.
- Dashboard with PRISMA counts, audit trail, and progress indicators
- Sign-in, optional captcha-protected registration, and server-managed sessions
- Admin controls for password reset, account deletion, registration policy, checkout windows, and PDF upload size
- Multi-project workspace with per-project navigation
- Team membership management with owner safeguards
- RIS and BibTeX import with provenance and review flow
- Dedup candidate review with side-by-side comparisons
- Title/abstract screening with append-only decisions, undo route support, and temporary checkout-based queue control
- Full-text review with PDF upload, validation, streaming, DOI linking, and temporary checkout-based queue control
- Dedicated bulk PDF upload page with paper assignment, DOI link pills, filtering, and per-file results
- Conflict handling in full-text and extraction phases
- Extraction templates (text, single-choice, multi-choice)
- Extraction submissions with temporary checkout-based queue control, consensus routes, and configurable extraction voting
- Export and report validation endpoints
- Theme preferences (light, dark, system)
- Path-based routing and refined UI components
- Accessible navigation links, a skip link, keyboard menus, visible focus, and responsive review layouts
Page navigation uses links, including support for opening views in a new tab. Press Tab to reveal Skip to main content, which bypasses the header and sidebar. Keyboard focus is visible on links and controls, and page changes move focus to the main content.
The account menu supports Enter/Space to open or activate an item, Up/Down to open or move through items, Home/End to reach the first or last item, and letters to find an item by its initial. Escape closes the menu and returns focus to its button; Tab/Shift+Tab closes it and moves focus out. Opening mobile navigation focuses its first link; Escape closes it and returns focus to the navigation toggle.
On smaller screens, screening and duplicate queues start collapsed below the review panels. Full-text decision controls and extraction forms appear before their PDF viewers. The workflow diagram adapts from six columns to three, two, or one as the screen narrows.
In Full text, open PDFs Upload beside Reviewed to manage PDFs for all full-text papers, including papers already reviewed. The table has DOI link pills for retrieval, search, pagination, and an Only papers missing PDFs filter.
- Choose Select PDFs and select one or more files, or choose a PDF from a paper's row.
- Assign each selected file to its paper. Choosing a paper with an existing PDF replaces its attachment.
- Choose Upload … PDFs to upload the batch. Each file shows its result, and failed files can be retried without uploading successful files again.
Files upload individually in sequence, with the configured size limit applied to each PDF. Existing PDFs can also be replaced or deleted from the table; deletion uses a browser confirmation. PDF upload is optional for full-text decisions.
- Create an account, sign in, and create a review project.
- Import records from RIS or BibTeX with batch provenance.
- Review deduplication candidates (when present).
- Screen title/abstract decisions (include/maybe/exclude).
- Advance studies to full-text, upload/validate PDFs, and review outcomes.
- Resolve conflicts and advance eligible studies to extraction.
- Define extraction templates and collect reviewer submissions.
- Build extraction consensus and validate/export reporting output.
- Frontend: Next.js App Router + React UI
- Backend: Next route handlers under
app/api - State: Configurable server-side store
- Default: JSON state file with atomic writes
- Optional: PostgreSQL-backed state (
PRISMATICA_STORAGE_MODE=postgres)
- Auth: Signed HTTP-only cookie sessions with server-side checks in route handlers
- Users/Settings persistence:
- Included in the active state store (JSON or PostgreSQL)
- Optional incremental PostgreSQL sync while workflows stay on JSON (
PRISMATICA_USERS_SYNC_POSTGRES=true)
- Files: PDF storage adapter
- Local filesystem by default
- MinIO/S3-compatible object storage via AWS SDK (
PRISMATICA_OBJECT_STORAGE_PROVIDER=minio), with optional local fallback during migration
Core modules:
lib/serverStore.ts: persistence, business logic, and workflow mutationsscripts/postgres-state-io.mjs: PostgreSQL read/write adapter for full app statelib/postgresUsersSync.ts: incremental PostgreSQL sync for users and auth/checkout settingslib/serverAuth.ts: session and cookie handlinglib/serverRoute.ts: route helpers (auth, JSON, file responses)lib/pdfStorage.ts: PDF storage abstraction (local/minio)lib/objectStorage.ts: MinIO/S3-compatible object storage clientlib/workflow.ts: stage and decision-state progression logiccomponents/prisma-review-app.tsx: main application shell and view orchestrationcomponents/prisma-review-ui.tsx: reusable UI presentation components
- Node.js 20.9 or newer
- npm
Ubuntu/Debian install example:
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejsVerify:
node --version
npm --versionnpm run dev # Next.js development server
npm run dev:https # Dev server with experimental HTTPS
npm run build # Production build
npm run start # Production server (defaults)
npm run start:prod # Production server bound to 127.0.0.1:3000
npm run check # TypeScript and API authentication checks
npm run check:navigation # Navigation, keyboard, and responsive layout checksNo demo reviewer accounts are pre-created.
- Register the first reviewer from the sign-in page.
- Create projects and invite members from project settings.
A separate administrator account is seeded on startup by default:
- Email:
admin@prismatica.local - Password:
change-me-admin
For non-local environments, set PRISMATICA_ADMIN_PASSWORD explicitly.
For production, set a stable session secret and move data outside the repo:
Default behavior when PRISMATICA_DATA_FILE is not set:
- State file:
./data/prismatica-state.json(relative to the project root) - PDF folder:
./data/pdfs/
Production example:
export PRISMATICA_SESSION_SECRET="replace-with-a-long-random-string"
export PRISMATICA_DATA_FILE="/var/lib/prismatica/prismatica-state.json"
npm run build
npm run start -- --hostname 0.0.0.0 --port 3000Optional environment variables:
export PRISMATICA_INVITE_PASSWORD="temporary-password-for-invited-users"
export PRISMATICA_ADMIN_EMAIL="admin@example.com"
export PRISMATICA_ADMIN_PASSWORD="replace-this-default-admin-password"
export PRISMATICA_REGISTRATION_ENABLED="false"
export PRISMATICA_CAPTCHA_SECRET="replace-with-a-long-random-string"
export PRISMATICA_SECURE_COOKIES="true"
export PRISMATICA_ALLOWED_ORIGINS="https://prismatica.example.org"
export PRISMATICA_USERS_SYNC_POSTGRES="true"
export PRISMATICA_STORAGE_MODE="postgres"
export PRISMATICA_OBJECT_STORAGE_PROVIDER="minio"
export PRISMATICA_S3_ENDPOINT="http://127.0.0.1:9000"
export PRISMATICA_S3_REGION="us-east-1"
export PRISMATICA_S3_BUCKET="prismatica-pdfs"
export PRISMATICA_S3_ACCESS_KEY="minio-access-key"
export PRISMATICA_S3_SECRET_KEY="minio-secret-key"
export PRISMATICA_S3_FORCE_PATH_STYLE="true"PRISMATICA_SESSION_SECRETsigns session cookies; keep it private and high-entropy.- Use a long random value (at least 32 bytes of entropy).
- Store it in environment variables or a secrets manager, not in git.
- Use a different secret per environment (development, staging, production).
- Rotating the secret invalidates all active sessions and requires users to sign in again.
Generate a strong secret:
openssl rand -base64 48or
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"Notes:
- Use
PRISMATICA_SECURE_COOKIES=trueonly when serving over HTTPS. PRISMATICA_REGISTRATION_ENABLED=falsedisables public registration for new data files.- Uploaded PDFs are stored under a sibling
pdfs/folder nearPRISMATICA_DATA_FILEunlessPRISMATICA_OBJECT_STORAGE_PROVIDER=miniois enabled.
Prismatica uses server-side checks for the main web and API security boundaries:
- Sessions are signed with
PRISMATICA_SESSION_SECRETand stored in HTTP-only cookies. PRISMATICA_SECURE_COOKIES=truemarks session cookies as secure when the app is served over HTTPS.- Project pages are guarded before the workspace shell renders; unauthenticated
/projects/*requests redirect to sign-in, and unauthorized authenticated users receive a not-found response. - API routes that read or mutate review data require an authenticated session and enforce project member, project owner, or admin permissions server-side.
- Login and registration attempts are rate-limited to 10 attempts per minute by submitted email and client IP. The current limiter is in-memory, so use a shared store such as Redis or PostgreSQL if you run multiple app instances.
- Registration can be disabled with
PRISMATICA_REGISTRATION_ENABLED=false; when enabled, registration uses a signed captcha challenge. - Unsafe API methods (
POST,PUT,PATCH,DELETE) are protected by a same-originOrigin/Referercheck inproxy.ts. Extra trusted origins can be added with comma-separatedPRISMATICA_ALLOWED_ORIGINS. - PDF uploads are restricted to PDF content, validated by an admin-configurable size limit (25 MB by default) and header, and served with private no-store caching and
X-Content-Type-Options: nosniff.
PDF uploads use base64 JSON, so request bodies are approximately one-third larger
than the PDF file. next.config.mjs sets experimental.proxyClientMaxBodySize to
150 MB to accommodate the admin form's maximum 100 MB PDF setting. The saved PDF
limit (for example, 25 MB) remains enforced by the client and API. If configuring
larger files outside the admin form, increase the proxy buffer limit accordingly.
Changes to the Next.js configuration require a server restart and, in production,
a rebuild before restarting.
Set PRISMATICA_OBJECT_STORAGE_PROVIDER=minio with the PRISMATICA_S3_* variables above to store new PDF uploads in MinIO or another S3-compatible service. Object keys use:
reports/<project-id>/<report-id>-<sha256>.<extension>
During migration, MinIO mode still reads from the local PDF folder when an object is missing. Disable that fallback after backfill and verification:
export PRISMATICA_PDF_LOCAL_FALLBACK="false"Backfill existing local PDFs into MinIO:
npm run backfill:pdfs:minio -- --dry-run
npm run backfill:pdfs:minioThe backfill verifies each PDF checksum before upload and updates report storagePath values to the MinIO object key. It works with either JSON state or PRISMATICA_STORAGE_MODE=postgres.
If you are rebuilding review data from scratch, you can migrate only accounts and auth preferences first.
What this migrates:
authSettings.registrationEnabledauthSettings.screeningCheckoutWindowMinutesauthSettings.extractionCheckoutWindowMinutesusers(profile fields, password hash/salt, admin flag, theme)
What this does not migrate:
- projects, studies, reports, decisions, extraction, events, dedup candidates
Run:
export DATABASE_URL="postgresql://user:password@127.0.0.1:5432/prismatica"
export PRISMATICA_SOURCE_STATE_FILE="./data/prismatica-state.json" # optional
npm run migrate:users:postgresTo keep PostgreSQL users/auth settings updated while the app still uses JSON for review workflows, enable runtime sync:
export PRISMATICA_USERS_SYNC_POSTGRES="true"When enabled (and DATABASE_URL is set), these mutations sync to PostgreSQL automatically:
- registration
- profile updates (including password changes)
- admin auth-settings updates
- invited user creation
- admin users management
Note: when PRISMATICA_STORAGE_MODE=postgres is enabled, full review state is persisted in PostgreSQL relational tables; PRISMATICA_USERS_SYNC_POSTGRES is only needed for the legacy JSON-primary transition mode.
Schema file:
db/users_preferences.sql
To make PostgreSQL the primary store for review workflow state (projects, studies, reports, decisions, extraction, events), set:
export PRISMATICA_STORAGE_MODE="postgres"
export DATABASE_URL="postgresql://user:password@127.0.0.1:5432/prismatica"Behavior:
- Prismatica persists users, auth settings, projects, imports, studies, reports, decisions, extraction state, events, and dedup candidates in PostgreSQL relational tables.
- If the relational tables are empty on first start, Prismatica can bootstrap from
PRISMATICA_DATA_FILE(or default./data/prismatica-state.json) when available. - Legacy
app_state_storeis read only as a compatibility fallback during transition; new writes target relational tables. - After cutover, keep JSON only as backup/export fallback.
npm run dev -- --hostname 0.0.0.0 --port 3000Then open http://<server-lan-ip>:3000.
If the LAN IP changes, add it in next.config.mjs under allowedDevOrigins and restart the dev server.
npm run build
npm run start -- --hostname 0.0.0.0 --port 3000Then open http://<server-hostname-or-ip>:3000.
Prismatica is intended to run behind Caddy with Next.js bound to localhost (127.0.0.1:3000).
Deployment assets:
deploy/caddy/Caddyfile: IP mode withtls internaldeploy/caddy/Caddyfile.letsencrypt: domain mode with public certificatesdeploy/caddy/prismatica.service: systemd service for the Next.js app
Use this when no domain is available.
- Edit
deploy/caddy/Caddyfileand replace203.0.113.10with your server IP. - Activate config:
sudo cp deploy/caddy/Caddyfile /etc/caddy/Caddyfile
sudo systemctl reload caddy- Install and start app service:
sudo cp deploy/caddy/prismatica.service /etc/systemd/system/prismatica.service
sudo systemctl daemon-reload
sudo systemctl enable --now prismatica- Trust Caddy local root certificate on each client:
# On server
sudo cp /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt /tmp/caddy-local-root.crtDebian/Ubuntu client trust:
sudo cp caddy-local-root.crt /usr/local/share/ca-certificates/caddy-local-root.crt
sudo update-ca-certificatesFedora/RHEL client trust:
sudo cp caddy-local-root.crt /etc/pki/ca-trust/source/anchors/caddy-local-root.crt
sudo update-ca-trustUse this for browser-trusted public certificates.
- Point DNS A/AAAA records to your server.
- Edit
deploy/caddy/Caddyfile.letsencryptand set your domain. - Activate config:
sudo cp deploy/caddy/Caddyfile.letsencrypt /etc/caddy/Caddyfile
sudo systemctl reload caddyCaddy will automatically request and renew certificates.
# IP-only mode (internal CA)
sudo cp deploy/caddy/Caddyfile /etc/caddy/Caddyfile
# Domain mode (Let's Encrypt)
sudo cp deploy/caddy/Caddyfile.letsencrypt /etc/caddy/Caddyfile
sudo systemctl reload caddy- Set a strong
PRISMATICA_SESSION_SECRET - Set a strong
PRISMATICA_ADMIN_PASSWORD - Enable
PRISMATICA_SECURE_COOKIES=truewhen using HTTPS - Set
PRISMATICA_ALLOWED_ORIGINSif the public browser origin differs from the app server origin behind a proxy - Keep Next.js behind localhost and reverse proxy through Caddy
- Restrict filesystem permissions on data and PDF storage
- Open only required firewall ports (
443; optionally80for ACME HTTP challenge) - Keep Caddy internal CA materials restricted to trusted admins (if using internal CA mode)
Administrators can set Registered Users → Review Settings → Audit history limit. The default is 100 events across all reviews, configurable from 1 to 10,000. The newest events are retained; lowering the limit removes older events, and raising it cannot recover events already removed. This applies to file and PostgreSQL storage. Existing settings without a history limit use 100; PostgreSQL adds the preference column automatically when its schema is initialized.
Dedup exclusions, keeping both entries, reopening pairs, and bulk inclusion appear in the project audit. Previously missing events cannot be recreated by this fix.
Run npm run check:audit for isolated regression checks using temporary data and
a simulated PostgreSQL client; it does not start a server or contact a database.
Global review preferences are stored in review_settings and exposed through
/api/admin/review-settings using the reviewSettings application-state field.
Prismatica is distributed under the MIT License.
