All projects

Content management

pacod-cms

One stack, many editorial websites — articles, advertising and statistics, with AI translation built in.

pacod-cms is an open-source content management system for editorial websites. One installation hosts many sites, each with its own database, files and cache; editors work in a multilingual studio, and readers get fast, host-routed public sites.

  • Apache-2.0
  • Version 0.0.1
  • Preview

Features

  • Editorial core

    Articles, categories, authors, tags and standing pages, with a shared image library across the website.

  • Multi-site by design

    Each website has its own MongoDB database, files and cache, and answers at the hosts it claims.

  • Advertising

    Advertising bookings with an HTML5 banner builder, plus a link-in-bio page for social profiles.

  • Statistics

    Reader, advertising and crawler statistics, so editors see what is read and what is crawled.

  • Multilingual

    Content in many languages with a configurable original language; the studio itself in English, Bulgarian, German and Russian.

  • AI translation

    Translate an article or a whole website as a job; each answer is refused unless it keeps the article's markup exactly.

  • Organisations and roles

    Organisations, websites and invitations, with roles per organisation and per website held in pacod-id.

  • Configurable PDFs

    Article PDFs as a background job, in the website's print layout or a two-column scientific format, with the abstract and keywords taken from the article or written by AI.

  • Journal issues

    Articles gathered into an issue, one PDF per language with a cover, an editorial, contents and numbered pages, published in the website's archive.

Where it fits

Diagram: how the pacod projects fit together LLM calls Tap a project to see what it connects to.

One stack, many websites

pacod-cms runs many websites, for many organisations, from one stack. Each website has its own MongoDB database, its own folder of files and its own cache entries, and answers at the hosts it claims. Anyone who signs up can create an organisation, become its admin, create websites in it and invite people to work on them; a platform admin sets the limits.

The design assumes a website's owner may be hostile to everyone else on the installation. Websites run script their owners wrote — HTML5 banners, tag managers, article bodies — and the platform has to keep that script, and that data, where it belongs.

How the parts fit

Part What it does
pacod-cms-core A library: documents, services, the tenancy, the control plane, seed content, the design model, and a Java port of pacod-id's rules engine.
pacod-cms-site The public site for every website at once: pages, read-only JSON under /web/v1/**, media, traffic counters.
pacod-cms-studio The studio: the portal for organisations, websites and members, the editing screens, background jobs, AI.
Caddy The front door, routing by host: the studio's host to the studio, pacod-id's host to pacod-id, every other host to the site.
Gotenberg Renders the PDFs of articles and journal issues from the site's print views.

The two applications never call each other. Both use MongoDB directly, so an edit made in the studio reaches the public site through the database; the site's caches expire after 60 seconds instead of being evicted, so an edit is live within a minute.

From a host to a page

Every unit of work belongs to exactly one website, bound for its duration.

  1. Caddy hands the request to the site. In production it requests a website's certificate at the first visit, but only after the site confirms the host belongs to an active website.
  2. The site looks the host up in the control plane before anything else runs. A host no active website claims gets a plain 404, never a default website.
  3. With the website bound, every MongoDB operation goes to that website's database. With none bound, the database layer throws rather than guess.
  4. The page type's layout — regions of ordered sections — is resolved against the website's template set, and each section's data is looked up first. Templates never query; a section whose data fails is left out and logged, and the page still renders.

In the studio the website comes from the path (/sites/<id>/mgmt/…); background work binds the website it is for.

Where data lives

Where What
pacod_cms The control plane: organisations, websites, domains, platform settings, the job queue.
site_<id> One database per website: articles, pages, images, banners, menus, settings, layouts, theme, statistics.
sites/<id>/ The website's files: article images, banner creatives, PDFs. A draft issue's PDFs sit in a private folder only the studio serves.
pacod-id People, sessions and role bindings. The CMS stores no accounts.

A website's identity — name, logos, contact details, third-party ids — is data, not markup. A third-party id renders only when it is set and has its vendor's shape, so a fresh website loads no tracking at all. The website's original content language is the last fallback for a missing translation and the legally governing text.

Design without touching code

Under Design in the studio, each website picks a template set — editorial, a magazine with a column beside the content, or minimal, one quiet column — and arranges a layout per page type: home, article, category, latest, standing page and search. Sections include lists of articles (latest, a category or the most read), rich text, a call to action, a search box and the social links. A theme sets colours with night-mode values, fonts and widths.

Switching sets first shows what would be lost, and nothing is removed: sections the new set cannot show are kept and come back on a switch back. Layouts and the theme stay drafts until someone allowed to publish them does. The preview shows drafts from the website's own address, at desktop, tablet or phone width, through a signed token valid for 30 minutes, and counts nothing.

A new template set is code an operator installs, not an upload: a Thymeleaf template can do anything the site can.

PDFs and journal issues

Long work runs in the studio as a job, one organisation's at a time, with a live log and a cancel button. PDF export is one: Gotenberg renders each article into one PDF per language it is written in, and the article links its PDF once it exists.

  • Two formats. Standard is the website's print view. Scientific is a paper layout: a full-width head with the title, authors, abstract and keywords, then the text in two columns.
  • Two ways to make them. Code only takes the abstract and keywords from the article's description and tags. With AI has pacod-gw write an abstract, keywords and a short running title for each article and language. The article's text is never changed.
  • PDF settings hold each website's defaults: format and way, paper (A4, Letter or A5) and margins, the journal's name per language and its ISSN, a running head and page numbers. Any run can choose another format and way.

A journal issue gathers articles into one PDF per language. It has a volume, a number and a month, a title and an editorial per language, and an optional cover image; its articles are picked one by one or filled from its month, then put in order. Generating it renders a cover, the editorial, a table of contents with page numbers and then the articles, with a bookmark per article and the pages numbered throughout. A draft issue's PDFs are downloaded from the studio only. Publishing it puts the issue and its PDFs on the website at /issues and in its sitemap.

Security model

Who may do what

pacod-id owns accounts and roles. Tokens carry no roles: the studio follows pacod-id's feed of role bindings and decides every permission itself against the CMS's manifest (authz/cms.yaml). The roles are platform admin, provisioner, organisation admin, website admin, designer, editor, author and analyst, held at the platform, an organisation or a website. An author writes; an editor also publishes.

  • Every studio handler declares the permission it needs; one that declares none is refused. No role is implicit.
  • When pacod-id's feed has not answered for a minute, the studio answers 503 rather than decide with roles that may have been taken away. The public websites are unaffected.

Websites kept apart

  • The studio sits on a different registrable domain from the websites, or refuses to start. Otherwise a website's script could set cookies for the studio and defeat its CSRF protection.
  • The studio never serves a website's HTML or script. Draft and HTML5 banner previews are framed from the website's own origin.
  • Gotenberg is locked down: it can reach only the site's internal address and runs no JavaScript.
  • Only published articles reach a reader, on every route, list, feed, sitemap and PDF. A draft journal issue is never public.

AI that keeps its hands off your keys

Every AI call goes through pacod-gw; the studio never holds a provider's key. AI is off for new organisations by default: a platform admin switches it on per organisation, and a website's admin can switch it off for that website.

  • Article translation runs as a job. A translated body must keep its source's markup exactly — only alt, title, aria-label and placeholder may change — or it is refused.
  • Website translation covers standing pages, section names, menus, the link page and, if ticked, tags, without overwriting an edit made while it ran; at most 2,000 calls a run.
  • Writing help rewrites a selection six ways and suggests a summary and tags.
  • Alt text per language is suggested by a vision model from the picture itself.
  • Design assist drafts a theme or a layout from a few words, written only to the draft.
  • PDF front matter: for PDFs made with AI, an abstract, keywords and a running title per article and language, checked as plain text. They are reused until the article is edited.

The gateway sees opaque ids only, never names or e-mail addresses.

Three environments, one Makefile

dev stage prod
Front door HTTP, plus HTTPS from Caddy's local CA HTTPS (Let's Encrypt) HTTPS (Let's Encrypt)
pacod-id's mail caught by mailpit caught by mailpit your relay
AI mock gateway mock gateway your pacod-gw, or off
Demo websites yes yes none

Stage is production's configuration with mail caught and AI mocked. make check refuses to start while a secret is missing or the configuration is unsafe. Back up .env, the MongoDB databases, the websites' files and pacod-id's PostgreSQL. The studio's /mgmt-health is liveness only: a studio that cannot reach pacod-id stays healthy while its screens answer 503, and says so in its log.

Every environment runs on pacod-net, the Docker network all pacod projects share. The CMS's own services — the site, the studio, MongoDB and Gotenberg — are in one compose file; its own copies of the shared parts — Caddy, pacod-id with its PostgreSQL, the development gateway and the mail catcher — are in a second, which the Makefile loads alongside. Only one stack running the shared parts fits on a machine. pacod-demo loads only the first file and runs the CMS with the other projects behind one Caddy, pacod-id, PostgreSQL and gateway; it is online at demo.pacod.io.

Limits and further reading

Known limitations in v1:

  • A website's identity (siteSettings) has no studio screen yet; it is edited in the database.
  • The demo content is in English only.
  • The members list does not show platform-wide roles.

The roadmap lists what v1 defers, among it a write API, a gateway key and AI budget per organisation, a separate worker, S3 storage and content importers.

In the repository:

Screenshots

Tech stack

Written in
  • Java
Tech stack
  • Java 25
  • Spring Boot 4.1
  • Thymeleaf
  • MongoDB
  • Maven
  • Caddy
  • Gotenberg
  • Docker

Quick start

A developer machine
make initmake upopen http://studio.localhost:8000/
Another website from the command line
make site-create SLUG=acme NAME="Acme News" HOSTS=acme.localhost DEMO=1

Works with

  • pacod-id

    The studio signs people in through pacod-id, which also holds their roles per organisation and per website.

  • pacod-gw

    AI features go through the gateway — the studio never holds a provider's key.

Licence

Apache-2.0

The project's own code is Apache-2.0. The studio bundles the TinyMCE editor (GPL-2.0-or-later), so the studio as built is redistributed under GPL-3.0 terms as a whole. The public site does not contain it.