A content system you own

Not a service. Not a headless API. Not a dashboard you log into somewhere else. Vinum is a handful of packages you import: its admin mounts inside your route tree, its loaders run in your server, and your content lives in your own store, on your own disk. Nothing to keep alive, no key to rotate.
Fig. 1Exploded
An exploded view of a Vinum install: your React Router app, its route file, the wiring module, your blocks, the admin, the store adapter and your content file, drawn along one assembly axis.10121416182022
Select a part to examine it

Every part of an install, drawn apart. Assembled, all of it sits inside the boundary of an app you already deploy — which is the whole of the argument.

Reference numerals
10
Your React Router appThe boundary everything else sits inside. Vinum adds no process of its own, no container beside yours and no second origin.
12
app/routes.tsvinumRoutes() returns route definitions. You choose the mount path and add your own routes around them.
14
app/vinum.server.tsThe whole wiring — site, store, media, auth — in one file, in your repo. It is Listing 1 further down this sheet.
16
app/blocks.tsxYour own blocks, registered through the same defineBlock the core blocks use. This figure is one of them.
18
The admin, at /adminMounted inside your route tree and painted by your nineteen variables. A screen of your site, not a dashboard somewhere else.
20
The Store adapterFive methods over a key/value interface. fs, Deno KV, memory, or one you write in an afternoon.
22
data/store.jsonYour content, on your disk, as documented keys and plain JSON. One command takes all of it out again.

The admin is a screen of your site

An admin at /admin, pages at /<slug>, drafts, revisions, scheduled publishing, media, forms and redirects — inside the app you already deploy.

The figures on this page are not mockups. Vinum renders no colour of its own: nineteen CSS variables style the public renderer and the entire admin alike, and this site sets them once. So the admin is ink on bond because the page is, and Fig. 2 and Fig. 3 are what you would actually see.

Fig. 2
The landing page in edit mode: a paragraph outlined for editing in place, with the block settings panel open beside it.
Editing happens on the page it renders on, in the layout it will actually have, with the site's real components. The inline editor and the public renderer are one code path in two modes — which is why this figure is a screenshot of the page you are reading.
  1. the block being edited, outlined in place
  2. its settings, for what cannot be typed inline
  3. the same window chrome the public page uses

Five commitments

Each one costs something to keep. That is what makes it a commitment.

What is claimed is:

  1. Your data outlives our code

    Keys are strings, values are plain JSON. No ORM, no migrations framework, no proprietary encoding — anyone with a SQLite viewer can read your content. Lock-in is a bug.

  2. Boring on purpose

    Web-standard Request and Response. Web Crypto. A key-value interface with five methods. Every dependency we add is an upgrade you will one day have to perform, so we add almost none.

  3. Safe by construction, not by discipline

    Nothing authored is trusted — not from an editor, not from an import file, not from an agent holding a tool. Every write passes through a pure sanitizer that drops what it does not recognise. No raw HTML from data reaches output, ever.

  4. The CMS has no taste — it has one place to put yours

    Vinum ships structure and a styling contract; you ship the values. An editor chooses what a block says and which variant it wears, never its colour or its spacing. That boundary is why a client can edit all afternoon without breaking the layout.

  5. Editing happens where the content lives

    On the page it renders on, in the layout it will actually have, with your real components. Not a form on the left and a preview on the right.

Fig. 3
The Vinum block picker, showing core blocks alongside this site's own blocks.
Core ships the blocks nearly every site needs. The ones grouped under This site are registered in this repo through the same defineBlock API the core blocks use — including the window, the menu rows and the figure plate you have been reading. Extension is not a plugin loader; it is TypeScript in your own codebase.
  1. core blocks — the curated stock set
  2. this site's own, registered from its repo

You can leave

This section exists because the answer elsewhere is often "you cannot". Wix's own help centre states a Wix site needs to be hosted and operated on Wix's servers, so leaving means rebuilding rather than moving.

Vinum exports everything as one JSON file. One command, on a site you have not touched in a year, without reading anything first.

Also from the admin, or one call in code. It is not a special export mode — it is the same code path the Backup screen uses every day. Run it before you commit to Vinum, not after: an escape route you have not checked is not one.

Listing 1 — terminal
npx vinumcms export
Listing 2 — store-backup.json
{  "version": 1,  "exportedAt": 1754481600000,  "entries": [    { "key": ["page", "about"], "value": { "slug": "about", "title": "About" } },    { "key": ["rev", "about", 1754000000000], "value": {} }  ]}
The whole format: a flat list of key/value pairs, using the same documented keys the running site uses.

What it refuses to be

  • A headless API

    If you want to query content over GraphQL from a separate frontend, Vinum is the wrong tool. It is deliberately coupled to the app that renders it — that coupling is what buys inline editing and one-deployment operations.

  • Multi-tenant SaaS

    One install, one site, one filesystem. Anything else changes every security assumption in the codebase.

  • A plugin marketplace

    Extension happens in your own codebase, in TypeScript, with types. There is no plugin loader and there will not be one. Of 11,334 WordPress vulnerabilities disclosed in 2025, 91% were in plugins.

  • A page builder with sixty blocks

    Core ships the blocks nearly every site needs, as a stock set you pick from and edit in place — not a box of primitives, and not a palette that needs a search field. Your weird hero belongs in your repo.

  • A commerce platform

    Vinum grew inside a watch shop and the shop code stayed there. Vinum knows about pages, not products.

An honest comparison

Vinum is small on purpose, and small has costs. The last row is the one that matters most if you are choosing between these.

VinumWordPress
Extending itTypeScript in your own repo, through the same API the core blocks use.A plugin ecosystem — and 91% of 2025's 11,334 disclosed vulnerabilities were in plugins.
Getting outOne JSON file with documented keys, importable into a fresh install.Exporters exist, with fidelity that depends on which plugins wrote your content.
Where you editOn the page, in its real layout, with your components.Gutenberg. Sentiment across 340+ opinions is roughly a coin flip.
OperationsNone. It deploys with your app because it is part of your app.A PHP host, core and plugin updates, and a security surface to watch.
EcosystemAlmost none. If you need a feature nobody wrote, you write it.Enormous, and a real advantage. Whatever you need probably exists already.

Install it

Listing 3 — terminal
npm install @vinumcms/core @vinumcms/adapters \  @vinumcms/server @vinumcms/react @vinumcms/admin npx vinumcms doctor   # checks the three settings that fail silently
Then wire the module drawn as part 14 in Fig. 1, mount the routes, and sign in at /admin. The setup guide lives with the admin package.

The setup guide — install, wiring, the three build settings, and the nineteen theme variables.

The docs — core, server, react, admin, adapters, and mail if you send any.

Lees dit in het Nederlands