Koriander CMS
Koriander CMS combines static site generation like Hugo and a dynamic CMS experience like Ghost into one application.
Koriander is early stage and powers the author's personal blog at https://www.justus.pw. Read more about Koriander.
Try Koriander CMS
Here's how you can try Koriander CMS on your computer.
First, ensure that your computer can run the pipx. Learn how to install pipx.
Once pipx runs on your computer, install Koriander by typing the following command in your terminal.
pipx install koriander
When the installation finishes, your terminal should print the following:
[…]
These apps are now globally available
- koriander
done! ✨ 🌟 ✨
Run the following command in a new directory:
koriander
This should print a start up log and give you a log in link:
Log in with the following link:
http://localhost:8321/dev-login/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/
Operations to perform:
Apply all migrations: admin, auth, contenttypes, koriander, koriander_user, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
…
Log in with the following link:
http://localhost:8321/dev-login/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/
…
WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/
Open the http://localhost:8321/dev-login/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/ in your browser. You should now see the Koriander CMS.
When you first run the Koriander CMS, you should see a welcome message saying Koriander installed successfully.
Edit the current page by following the Edit page link in the top menu.
When you finish editing your page, render your entire Koriander CMS website with the render command:
koriander render
When you run koriander render in a terminal, it should print the following:
Writing static files to public/static...
157 static files copied to '/private/var/folders/k8/jnlz0xdn5jd48zttf3ktv7200000gn/T/tmp.BWupFFLrzH/public/static', 461 post-processed, 25 skipped due to conflict.
Static files collected to public/static
Seeding 0 redirect address(es)...
Seeding 6 URL(s) from sitemap.xml...
Rendered 404.html
Page has markdown body, but no summary, index.html
200 /index.html (from seed)
200 /robots.txt (from seed)
/Users/debian/.local/pipx/venvs/koriander/lib/python3.13/site-packages/koriander/views.py:221: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'koriander.models.Tag'> QuerySet.
paginator = Paginator(
200 /tags/index.html (from /sitemap.xml)
200 /index.xml (from /sitemap.xml)
200 /index.atom (from /sitemap.xml)
200 /index.atom.xml (from /sitemap.xml)
200 /redirects.json (from /sitemap.xml)
Crawled 7 page(s).
Render complete. Output in: public
koriander render outputs your site in a public directory.
You can upload this site directory to a static site host like GitHub Pages, Cloudflare, or Netlify.
Develop
Here's what you need to develop develop and maintain Koriander CMS.
Requirements
First, make sure that you've installed the following programs on your computer
Verify that uv works by running uv --version in your terminal. On the author's computer,
this prints the folllowing:
uv 0.11.19 (aarch64-apple-darwin)
Verify that direnv works by running direnv --version in your terminal. On
the author's computer, this prints the following:
2.37.1
Install packages
You're now ready to install all PyPI packages that Koriander CMS needs. Install the packages with the following command:
uv sync
Enable direnv
Enable direnv for this directory with the following command:
direnv allow
When direnv allow runs, it prints the following:
direnv: loading ~/projects/koriander/dev/.envrc
direnv: export +KRNDR_DEBUG +KRNDR_SECRET_KEY ~XPC_SERVICE_NAME
Migrate the development database
Create and migrate your development database with migrate command:
uv run src/koriander/manage.py migrate
This prints something similar to this:
You've enabled Django debug mode
System check identified some issues:
WARNINGS:
koriander.Page: (models.W047) SQLite does not support unique constraints with nulls distinct.
HINT: A constraint won't be created. Silence this warning if you don't care about it.
Operations to perform:
Apply all migrations: admin, auth, contenttypes, koriander, koriander_user, sessions
[…]
Start the development server with the following command:
uv run src/koriander/manage.py runserver
When the server starts, it prints the following:
You've enabled Django debug mode
You've enabled Django debug mode
Watching for file changes with StatReloader
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
June 28, 2026 - 06:23:49
Django version 6.0.4, using settings 'koriander.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/
Create a administrator user (superuser) with
username admin by running the next createsuperuser
command in your terminal and following the instructions:
uv run src/koriander/manage.py createsuperuser --username admin --email ''
The createsuperuser command asks you to type a password for your admin user:
Password: < type your password here >
Password (again): < type your password again >
Superuser created successfully.
Open the following address in your browser and log in with the admin user
that you've just created:
http://localhost:8000/koriander-internal/admin/
Test code
Use the following command to run all tests:
bin/test.sh
Format code
Use the following command to format your code:
bin/format.sh
Migrate database
Use this command to migrate your database:
uv run src/koriander/manage.py migrate
Create new migration files
When you change Koriander CMS models, run this command to make a corresponding migration file:
uv run src/koriander/manage.py migrate --update
Add a new syntax to CodeMirror
See codemirror/README.md.
Configuration variables
See the docs/configuration.md document for information on what environment variables you can pass to Koriander.
Open several Koriander sites at the same time
Run 3 sites:
KRNDR_SESSION_COOKIE_SUFFIX=1 XDG_STATE_HOME=$(mktemp -d) koriander serve 9001
KRNDR_SESSION_COOKIE_SUFFIX=2 XDG_STATE_HOME=$(mktemp -d) koriander serve 9002
KRNDR_SESSION_COOKIE_SUFFIX=3 XDG_STATE_HOME=$(mktemp -d) koriander serve 9003
UI Translations
This section describes how to update the Koriander CMS UI translations.
Updating UI translations
If you've changed any code or templates in the Koriander CMS UI, run the following command to extract all translated strings:
bin/update-translations
Expected output:
processing locale de
processing locale ja
Then, update the translations in the translations files in
src/koriander/locale/de/LC_MESSAGES.
For example, to update the German translations files, update the
src/koriander/locale/de/LC_MESSAGES/django.po file. Example for
a not yet tranlated string:
#: src/koriander/admin.py:83 src/koriander/admin_page.py:78
#: src/koriander/admin_page.py:227
msgid "Published"
msgstr ""
Here's what it looks like after translating it:
#: src/koriander/admin.py:83 src/koriander/admin_page.py:78
#: src/koriander/admin_page.py:227
msgid "Published"
msgstr "Veröffentlicht"
After you've updated your translations, run the following command to update
all .mo compiled translation files:
bin/build-translations
Expected output:
processing file django.po in …/src/koriander/locale/ja/LC_MESSAGES
processing file django.po in …/src/koriander/locale/de/LC_MESSAGES
You should now see the following files changing when you run git status:
On branch …:
…
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/koriander/locale/de/LC_MESSAGES/django.mo
modified: src/koriander/locale/de/LC_MESSAGES/django.po
Make a new commit with your .mo and .po file changes and create a pull
request for the Koriander repository.
Add a new UI language
Here's how to add a new language to the Koriander CMS UI.
Update the --locale flags in bin/update-translations to add a language.
Take for example Swedish with the two letter ISO 639 code
sv. Update the following lines and add --locale sv \ here:
uv run django-admin makemessages \
--locale ja \
--locale de \
--ignore LICENSES \
…
After you add sv:
uv run django-admin makemessages \
--locale ja \
--locale de \
--locale sv \
--ignore LICENSES \
…
Run the following command to add the new blank translations files:
bin/update-translations
Expected output:
processing locale de
processing locale ja
processing locale sv
Add all missing translations in your new Swedish translation file at
src/koriander/locale/sv/LC_MESSAGES/django.po.
Once you've added all your translations, run the following command to update
the Koriander CMS .mo files:
bin/build-translations
Expected output:
processing file django.po in …/src/koriander/locale/de/LC_MESSAGES
processing file django.po in …/src/koriander/locale/sv/LC_MESSAGES
processing file django.po in …/src/koriander/locale/ja/LC_MESSAGES
Add your .mo file to the REUSE.toml file in src/koriander/locale/REUSE.toml.
For Swedish, add the following lines at the end of the file:
# src/koriander/locale/REUSE.toml
[[annotations]]
path = ["sv/LC_MESSAGES/django.mo"]
# Update this line when you add translations
SPDX-FileCopyrightText = "2026 YOUR NAME"
# For compatibility with Koriander CMS, please license with AGPL-3.0-or-later
SPDX-License-Identifier = "AGPL-3.0-or-later"
How to make a new release
- Pick next version number. Example: 0.17.0
- In
CHANGELOG.md, insert this version number and today's date in the## [Unreleased]line to become## [0.17.0] - 2026-08-26 - Use the
uv versioncommand to set the same version inpyproject.toml:uv version 0.17.0 - Stage changes to
CHANGELOG.md,pyproject.toml, anduv.lockwithgit add CHANGELOG.md pyproject.toml uv.lock - Make a git commit with message
Bump to version 0.17.0:git commit -m "Bump to version $(uv version --short)"
Release files for koriander 0.25.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| koriander-0.25.0.tar.gz | 722.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| koriander-0.25.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.5 MB
Release files / koriander-0.25.0.tar.gz
| Download URL | koriander-0.25.0.tar.gz |
|---|---|
| Size | 722.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
208727f4bb05902986d9c5aaac24cb284eae38f31ae1dbf7c4667f19f105f4a2
|
|
BLAKE2b-256 checksum How to use checksums |
e8e0acd7612f51538f1be3a25c73d858213d3eb7ebead283362287ca1ab01b27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / koriander-0.25.0-py3-none-any.whl
| Download URL | koriander-0.25.0-py3-none-any.whl |
|---|---|
| Size | 819.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c6f829f9ffe90fe9012e3079901ec9e4010a3172fd2d69944cdc41acd1e87346
|
|
BLAKE2b-256 checksum How to use checksums |
375f84f4305f7be7f247644f22bc7c934cd39283abeca95f37306d4063c36508
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|