📑  Paper CMS  📝

A document-based headless CMS with API + back office.

Made primarly for editorial content management. Stock definitions are modelled after Schema.org vocabulary.

🏁  Usage

Live demo

👉  Check here for a Paper CMS online tryout. 🔁  Default dataset is reseted periodically. 🔑  Use good ol’ admin / password.

or… local quick start 📦

# Clone this repo
git clone [email protected]:JulianCataldo/paper-cms.git
cd paper-cms
code ./

# Install all dependencies
npm run deps:i

# Run and watch API + back office
npm run dev

# Open back office in browser
open http://localhost:7777

# Default credentials
# > User : `admin`
# > Password : `password`

# Edit API definition
code -r ./models/api-v1.yaml

# Created entries and uploaded medias + variations
# are stored in root project by default in `.data`
tree ./.data

or… with Docker Compose 🐳

cd ./docker
make rdc-std

# Data persistence folder
tree ./docker/std/.volume/.data

See the Makefile for all containers variants and shorthands.

Some views  👀

❄️  Features

Auto-generated forms from OpenAPI definitions


JSON Schema validation on run-time for both client and server sides


UI Schema augmentation (non-standard)


Settings are splitted in two different schemas at runtime. Standard compliant JSON Schema alongside non standard UI Schema.

JSON files database

All your textual data is hosted unded ./data/docs (root can be overriden with DATA_DIR). Run multiple instances of Paper CMS easily by setting different PORT. Quickly swip datasets : demo / tests / staging / production…

Image(s) upload + dynamic resize while fetching + caching

JWT authentication for both human editors and API consumers


Full live reload for models / client / server

When editing model definitions, server or client code, everything is re-runned/builded/bundled. Also, browser is automatically refreshed thanks to a long polling web socket.

You can also trigger browser refresh by hitting curl localhost:${PORT:-7777}/reload.

Document revisions history

Soft deletion, restore from trash

“Has many” relationship via references


for full examples.

Nested referenced documents editing

A “sub” document is just another document reference. There is no hierarchy or inheritance, only cross-references betweens entities. While you can create, edit or associate an existing document from the main document form, only $refs URI for child references actually gets recorded, not the entire subtree.

Everything can be a document collection, as soon as it make sense to reference the entity from elsewhere. Self-sufficient data should live embedded in the document itself.

There is no limit for nesting, though stacked modal editors force us to save only one (sub)document at a time: no full tree update propagation. This design choice is meant to prevent data-loss / infinite recursive nesting.

Schema.org inspired default definitions

While Schema.org states that:

The type hierarchy presented on this site is not intended to be a ‘global ontology’ of the world.

While true, it provides a great starting point with commonly used taxonomies and entities relationships. Schema.org is shining for content-driven web sites where SEO is crucial (but that’s just an excuse). While still in its infancy, JSON/LD is already bringing HATEOS concepts into real-life applications, step-by-step, organically. Regardless, feature-rich widgets are democratizing, and data crawling between third-parties is becoming more insightful.

OpenAPI 3 UI (Swagger)

The endgoal is to make Paper CMS a non-deterministic data ingestion / organization / retrieval system. At the end, an hypermedia API should be auto-discoverable. Still, it’s useful to rely on Swagger niceties for on-boarding an API. Moreover, the OpenAPI 3 specs is adding links, a notable concept for Hypermedia minded APIs.

Wysiwyg HTML Editor


under the hood. Basic additional HTML sanitization is done server-side.

Custom fields and widgets

🤔  Why?


We are seeing the emergence of different patterns in the content management world like:

  • Shared entities vocabularies
  • Automatic web form generation
  • Isomorphic user input runtime validation,
  • Entity / Collection oriented information architecture
  • Headless non-opinionated CMS APIs for JAMStack consumption
  • Hypermedia for non-deterministic data fetching via refs. | links | URIs
  • Plain files data storage for operations convenience (with some trade-offs)
  • Conventions over configuration, with extendabilities (models, widgets…)


All these concepts are explored at different levels of implementations in Paper CMS. While it’s still an experiment, the end-goal is to provide a lightweight solution suitable for projects which:

  • Has ten-ish max. editors in charge
  • Needs moderate authoring concurrency with silo-ed document edits
  • Might needs frequent content updates
  • Low needs for user-land data input
  • Are in an eco-system of focused services where caching / CDNs / whatever are dedicated

To sum up: Paper CMS is good for editors-driven web sites, but is not a good fit for users-driven web apps.

ℹ️  Project insights

⚠️ Work in progress: NOT FOR PRODUCTION ⚠️


Mono-repo. glued with PNPM recursive modules installation.

Major dependencies

  • Node.js
  • ESBuild
  • React
  • MUI
  • React Quill
  • JSON Schema React
  • AJV
  • Express
  • Sharp

Work in progress

  • Single media management
  • Batch media management
  • JSON/LD API output conformance
  • OpenAPI conformance
  • Swagger integration
  • Wider Schema.org support for stock definitions
  • Basic users account management

To do

  • Media metadata with EXIF + IPTC support
  • API collections’ pagination
  • Automatic bi-directional relationships (“Is part of many” <=> “Has many”)
  • Custom data fetching and population widgets for APIs / social networks content retrieval

Features ideas

  • Might propose fully dynamic OpenAPI GUI builder right inside the back office, instead OR alongside YAML config.
  • Might propose MongoDB/pSQL/S3 alternative to bare file storage, for increased mass and/or concurrency needs, but decreased portability.
  • Might propose an option for storing media binaries in an S3 bucket.

    Yb, 88      `8b
    `"  88      ,8P
        88""""",gggg,gg  gg,gggg,     ,ggg,    ,gggggg,
        88    dP"  "Y8I  I8P"  "Yb   i8" "8i   dP""""8I
        88   i8'    ,8I  I8'    ,8i  I8, ,8I  ,8'    8I
        88  ,d8,   ,d8b,,I8 _  ,d8'  `YbadP' ,dP     Y8,
        88  P"Y8888P"`Y8PI8 YY88888P888P"Y8888P      `Y8
                          I8       ,gggg,   ,ggg, ,ggg,_,ggg,        ,gg,
                          I8     ,88"""Y8b,dP""Y8dP""Y88P""Y8b      i8""8i
                          I8    d8"     `Y8Yb, `88'  `88'  `88      `8,,8'
                          I8   d8'   8b  d8 `"  88    88    88       `88'
                          I8  ,8I    "Y88P'     88    88    88       dP"8,
                              I8'               88    88    88      dP' `8a
                              d8                88    88    88     dP'   `Yb
                              Y8,               88    88    88 _ ,dP'     I8
                              `Yba,,_____,      88    88    Y8,"888,,____,dP
                                `"Y8888888      88    88    `Y8a8P"Y88888P"


View Github