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 that need both speed 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, and checkout windows
- 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
- 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
- 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 API routes under
app/api - State: Server-side JSON store with atomic writes
- Auth: Signed HTTP-only cookies and session checks in server routes
- Files: Uploaded PDFs stored on disk by default, or in MinIO/S3-compatible object storage
Core modules:
lib/serverStore.ts: persistence, business logic, and workflow mutationslib/serverAuth.ts: session and cookie handlinglib/serverRoute.ts: route helpers (auth, JSON, file responses)lib/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 type-checkNo 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_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.
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.
If desired, PostgreSQL access can later move from custom SQL/state-IO helpers to Prisma ORM.
Possible benefits:
- Typed schema and generated client
- Migration history tracking
- Easier per-entity evolution as the data model grows
Suggested staged approach:
- Add Prisma schema and model
users,auth_settings, then validate parity. - Expand to review entities (
projects,studies,reports,decisions, extraction tables). - Replace
app_state_storeblob persistence with normalized Prisma-backed tables. - Keep existing API contracts stable during the swap.
This is optional and can be scheduled after the current PostgreSQL + MinIO stabilization.
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 - 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)
Run type checks:
npm run checkCreate production build:
npm run buildStart production server directly (without reverse proxy):
npm run start -- --hostname 0.0.0.0 --port 3000For production deployments, prefer the Caddy reverse-proxy pattern above.
