-
released this
2026-10-13 22:42:51 +02:00 | 12 commits to main since this releaseThis release pages the media library, remembers your page size on the article, activity and media lists, and previews image variants on a real sample photo. The media list API changes shape, so read the upgrading notes if you call it.
New
Media library
- The media library is now paged, with a page size of 5, 25, 50 or 100. The image picker in the editor uses the same pages.
Image variants
- Creating a variant shows a preview rendered from a real sample image, so the size estimate and the before and after comparison match what visitors will get.
Improvements and fixes
- The article, activity and media lists keep their page and page size in the address, so a reload or a shared link opens the same view.
- The Integration page is now fully translated into French.
API
- Changed:
GET /v1/media/answers{ items, total, page, size, counts }instead of{ items, next_cursor, counts }. It acceptspage(default1) andsize(5,25,50or100, default25). An invalid value answers400. - Changed:
GET /v1/media/no longer acceptscursororlimit.
Under the hood
- Replace the cursor and limit of the media list query with a page and a size in the image domain and the list use case.
- Add specifications, design checks and Go and Vitest suites for the paged media screens.
Upgrading
- Replace the binary, or pull the new image. There is no schema change and no new environment variable.
- Any script or site that reads
GET /v1/media/must now readtotaland loop overpageinstead of followingnext_cursor.
Downloads
-
released this
2026-10-13 03:28:24 +02:00 | 18 commits to main since this releaseThis release adds paginated, searchable article lists, links in the editor and an export of the activity log, and removes the article length limit. The article list API changes shape, so read the upgrading notes if you call it.
New
Articles
- The article list is now paged, with a search box, an Online and Draft filter, sortable columns and a page size of 5, 25, 50 or 100. Counts for All, Online and Draft stay fixed while you search.
- Articles have no length limit any more, and a new article can be created without a body.
Editor
- Insert, edit and remove links directly in the editor.
Activity log
- The summary cards at the top are now filters: select one to narrow the list, select it again to clear it.
- A new Export menu downloads the filtered events as a CSV file.
- The date range also accepts "All time".
Integration
- The Integration page has a new Images section covering image variants, and its code snippets now share one format.
Improvements and fixes
- Each page now sets its own browser tab title, in the form "Page · RenewCMS", instead of keeping the title of the first page loaded.
- Registering now waits for the session to be refreshed before showing the pending-approval screen, which fixes a redirect back to the sign in page.
- The media and variant screens, the navigation contrast and the shell colors follow the design spec more closely.
- The development workflow on Linux no longer deletes the visual check output.
API
- Changed:
GET /v1/article/always answers{ items, total, page, size, counts }instead of a bare array. It acceptspage,size(5,25,50,100),q,status(onlineordraft) andsort(for exampletitle:asc; defaultupdated_at:desc). An invalid value answers400. Anonymous callers still only see online articles. - Changed:
POST /v1/article/andPATCH /v1/article/{id}no longer reject a body over 10,000 characters, andPOSTaccepts an empty body. GET /v1/activityandGET /v1/activity/exportacceptrange=all.
Under the hood
- Add the paged article query to the article repository contract, with accent-insensitive title search, and promote
golang.org/x/textto a direct dependency. - Add specifications, design checks and Go and Vitest suites for the new screens.
- Document the article list in
docs/api/articles.mdand link the API docs from the README.
Upgrading
- Replace the binary, or pull the new image. There is no schema change and no new environment variable.
- Any script or site that reads
GET /v1/article/must now read theitemsproperty of the response. Withoutpageandsizeyou get the first 25 articles, so loop overpageuntil you havetotalitems.
Downloads
-
v3.1.0-beta - Image variants Pre-Release
released this
2026-10-08 21:15:43 +02:00 | 33 commits to main since this releaseThis release adds image variants: named sizes and formats of your images, generated on first use and cached, plus a size limit for the activity log.
New
Image variants
- Owners and admins define named variants (width, height, fit, format and quality) in the new variants screens under Settings. Variants can be created, edited, enabled, disabled, purged and deleted.
- Add
?v=<name>to any uploaded image URL to get the variant. It is generated on the first request, cached, and served from the cache afterwards. The original is never changed, and URLs without?v=behave as before. - Each image in the media library shows its variants, and lets you generate or purge them one by one or all at once.
- The new Cache screen shows how many files are generated, the disk space used, how many requests were rejected and what has been unused for 30 days. Purge everything, unused files, one variant or one image.
- Output can keep the original format or be converted to JPEG, PNG or lossy WebP. Images are never enlarged. SVG and animated GIF are served as they are.
- Oversized sources are refused with a clear error instead of exhausting memory, and identical concurrent requests share one generation.
- The new screens are available in English and French.
Activity log
- Retention can now also be capped by size, in GB, in addition to its age. Whichever limit is reached first removes the oldest events. No cap is set by default.
Improvements and fixes
- Fix the missing routes of the integration pages.
- Harmonize the design of the latest screens with the design spec.
API
GET /v1/image-variantsandPOST /v1/image-variantslist and create variants.PUT /v1/image-variants/{name}replaces a definition,PATCHenables or disables it andDELETEremoves it with its files.POST /v1/image-variants/{name}/purgeremoves the generated files only.GET /v1/image-variants/cachereturns the cache statistics, andPOST /v1/image-variants/cache/purgepurges by scope (all,unused,variantorimage).GET /v1/media/{id}/variantslists the variants of an image.POST /v1/media/{id}/variantsgenerates all missing ones andPOSTorDELETE /v1/media/{id}/variants/{name}generates or purges one.- All of these routes are for owners and admins and accept a session only: an API key answers
403. GET /static/uploadedImages/<storedName>?v=<name>serves the variant. An unknown or disabled name, or awidth,height,formatorqualityparameter, answers400. A source over the size or pixel limit answers413. The response carries anETagand answers304toIf-None-Match.PATCH /v1/settingsalso accepts a maximum activity log size in GB, returned asactivity_retention_max_gb.
Under the hood
- Add the image variant domain, repositories, renderer, in-memory usage tracker flushed every 30 seconds, and use cases, behind the existing storage port so local and S3 storage both work.
- Document the feature in
docs/image-variants.md,docs/api.mdanddocs/architecture.md, and add the specifications, design screens and Go and Vitest suites.
Upgrading
- Replace the binary, or pull the new image. The variant tables are created automatically at startup. No variant exists until you define one.
- Three optional environment variables are new:
IMAGE_VARIANT_MAX_SOURCE_BYTES(10485760),IMAGE_VARIANT_MAX_SOURCE_PIXELS(50000000) andIMAGE_VARIANT_MAX_CONCURRENT_RENDERS(4). An existing install needs no change. - Generated variants are stored next to your images, in the upload directory or the S3 bucket, and count toward the storage you use.
- Generating a variant when an image is uploaded is not part of this release.
Downloads
-
released this
2026-10-07 16:04:23 +02:00 | 43 commits to main since this releaseThis release adds an activity log that records who did what on your site, and lets RenewCMS run on MySQL, MariaDB or PostgreSQL and keep uploaded images in an S3-compatible bucket.
New
Activity log
- The new Activity page lists every important action as a plain sentence, newest first, grouped by day: articles created, edited, published or deleted, images uploaded, renamed, merged or purged, accounts and invitations, and the system tasks (scheduled purge, update checks and downloads, restarts).
- Filter by search text, person, category, outcome and date range, with Today, 7 days, 30 days and a custom range. Click a summary card to apply its filter. Open any row to see its details, including what changed.
- Failed actions are recorded with a plain reason, and failed logins and similar events are flagged as security events.
- Owners and admins see everyone's activity, a summary strip (total, active people, failed actions, security events) and can export the filtered list as a CSV file. Any other role would only see its own activity.
- Article bodies are never recorded, only the change in size. Passwords, codes, file contents and storage paths are never recorded either.
- The Home page has a new Recent activity card with the last five events.
- Owners choose how long events are kept in the new Retention card in Settings: 30 days, 90 days (the default), 365 days or forever. Shortening the period asks for confirmation first. Older events are removed automatically once a day, and Settings shows the last cleanup and the next one.
- The new screens are available in English and French.
More ways to run RenewCMS
- Use MySQL 8, MariaDB 11 or PostgreSQL 16 and 17 instead of the local SQLite file by setting
DB_DRIVERtomysqlorpostgresand theDB_*connection variables. SQLite stays the default. - Keep uploaded images in an S3-compatible bucket (AWS S3, MinIO, Ceph, Cloudflare R2, Backblaze B2 and similar) by setting
STORAGE_DRIVER=s3. Image URLs do not change and the bucket stays private. - The update backup taken before a download now works with every database: SQLite as before, and a logical dump for MySQL, MariaDB and PostgreSQL. The newest 3 backups are kept.
- Docker Compose files for each setup are included:
docker-compose.mysql.yml,docker-compose.postgres.ymlanddocker-compose.s3.yml.
API
GET /v1/activityreturns the filtered, paged events as{ items, total, page, size }, newest first. It acceptsq,actor,category,outcome,security,range,from,to,pageandsize, and any invalid value answers400. Owners and admins see every event. Any other role only sees its own, and asking for another actor answers403.GET /v1/activity/summaryreturns the counts for the range, the previous period, the active people and a seven-bucket series. Owners and admins only.GET /v1/activity/exportreturns the same filters as a CSV attachment (when,actor,action,target,outcome,source,ip). Owners and admins only. Cells that start with=,+,-or@get a leading quote.- The three routes accept a session only: an API key answers
403with "This action needs a real session." - There is no
POST,PUT,PATCHorDELETEunder/v1/activity. Events can only be removed by the retention period. GET /v1/settingsnow also returnsactivity_retention_days,activity_last_cleanup_at,activity_last_cleanup_removedandactivity_next_cleanup_at.PATCH /v1/settingsacceptsactivity_retention_dayswith0(forever),30,90or365. Session and owner only, with403"Only the owner can do this." for anyone else. Any other value answers400.
Under the hood
- Decouple persistence and image storage behind driver registries:
persistence/databasefor the SQL drivers andinternal/infrastructure/storagefor the image adapters, with the domainimage.Storageport. Repositories no longer touch files. - Add the audit domain, an append-only repository, a recorder that never fails or blocks the caller, and the use cases that record events from inside each action.
- Add a scheduler for the activity cleanup, which runs once a day at or after 03:00 server time, alongside the media purge scheduler.
- Add the activity, Home card and Retention card specifications and QA procedures to
features/, the new screens todocs/design/, and Go and Vitest suites for each part. - Document the new variables in
docs/configuration.md, and the new setups indocs/deployment.mdanddocs/docker.md.
Upgrading
- Replace the binary, or pull the new image. The activity tables are created automatically at startup.
- New environment variables are all optional and default to the previous behaviour:
DB_DRIVER(sqlite),STORAGE_DRIVER(local),DATA_DIR(the directory ofDB_FILE), and theDB_*andS3_*variables listed indocs/configuration.md. An existing install needs no change. - Activity starts recording after the upgrade, and nothing earlier is added. The first cleanup applies the default retention of 90 days, so owners who want a longer history should change it in Settings.
- There is no automatic migration between databases or from the upload directory to a bucket. To move, follow the steps in
docs/configuration.mdanddocs/deployment.md. - Backups taken before an update cover the database only. With S3, back the bucket up with your provider.
Downloads
-
v2.9.0-beta - Media duplicate purge Pre-Release
released this
2026-10-05 23:58:54 +02:00 | 53 commits to main since this releaseThis release adds a duplicate purge to the media library: owners and admins can merge identical images in one action, or let RenewCMS do it on a schedule.
New
Media duplicate purge
- Purge duplicates from the Duplicates tab of the media library with the new toolbar button, or from the new Media cleanup card in Settings. A summary shows how many copies would be merged and how many articles would be updated before you confirm.
- For each group of identical images used in at least one article, the most used copy is kept, with ties going to the oldest. Every other copy is merged into it and the articles that used them are rewritten. Groups where no copy is used are left alone.
- If the library changes between the summary and the confirmation, nothing is purged and you are asked to check the new summary.
- Turn on automatic purge in Settings and choose an interval from 1 to 365 days, 30 by default. The first run happens one interval after you turn it on, then once per interval after each run.
- The Media cleanup card shows the last run, with the number of copies merged and articles updated, and the date of the next one.
- The purge is reserved for owners and admins. Editors get a clear message instead of the controls.
- The new screens are available in English and French.
API
GET /v1/media/purge-settingsreturns the automatic purge settings:auto_purge_enabled,auto_purge_interval_days,last_purge_at,last_purge_merged,last_purge_articles_updatedandnext_purge_at, which isnullwhile automatic purge is off.PATCH /v1/media/purge-settingsupdatesauto_purge_enabledandauto_purge_interval_days. An interval outside 1 to 365 answers400.GET /v1/media/purge-previewreturns what a purge would do, with afingerprintof the plan. It changes nothing.POST /v1/media/purgeruns the purge. Send the preview'sfingerprintto refuse a run if the library changed: a stale fingerprint answers409, and so does a purge that is already running.- All four routes answer
403with "Only owners and admins can do this." for any other role, and accept a session only, like the rest of the media routes.
Under the hood
- Add the purge domain, a SQLite repository for the single-row purge settings, and the use cases that build the plan, preview it, run it and decide when a scheduled run is due.
- Add an hourly scheduler, started with the server after a one-minute delay and stopped on shutdown. It reads the settings on every tick, so changes apply without a restart, and a failed run is retried at the next tick.
- Add the
RequireOwnerOrAdminmiddleware. - Add the media purge, scheduler and Settings card specifications and QA procedures to
features/, the new screens todocs/design/, and Go and Vitest suites for each part.
Upgrading
- Replace the binary, or pull the new image. The purge settings table is created automatically at startup. No new environment variable is required.
- Automatic purge is off after the upgrade. Nothing is merged until an owner or admin runs a purge or turns it on.
- A purge cannot be undone: merged copies are removed. Check the summary before confirming.
Downloads
-
released this
2026-10-05 16:33:31 +02:00 | 58 commits to main since this releaseThis release fixes the security headers added in v2.8.2-beta, which blocked parts of the web app in the browser.
Improvements and fixes
- The web app's icons now display again. They are bundled into the app instead of being fetched from an external icon service, which the security policy blocked.
- The light and dark mode script in the page is now allowed by the security policy, so the color mode no longer fails to apply on load.
- The security policy now allows WebAssembly, which parts of the web app need.
Under the hood
- Add a Vite plugin that bundles only the Lucide icons the app uses.
- Compute a hash for each inline script in the served page and add it to the
script-srcdirective, instead of allowing inline scripts in general. - Update the security headers tests.
Upgrading
- Replace the binary, or pull the new image. No schema change and no new environment variable.
- If you are on v2.8.2-beta, upgrade: that version's policy broke icons and color mode.
Downloads
-
released this
2026-10-05 16:24:12 +02:00 | 60 commits to main since this releaseThis release hardens the server with browser security headers on every response and makes the log in form clearer when the username is wrong.
Improvements and fixes
- RenewCMS now sends security headers on every response, including the web app and uploaded images: a Content Security Policy that forbids framing,
X-Content-Type-Options,X-Frame-Options,Referrer-PolicyandCross-Origin-Opener-Policy. WithENVIRONMENT=productionit also sendsStrict-Transport-Security. - The log in form now tells you when you typed an email address instead of your username, and when the username is longer than 20 characters, instead of only asking for at least 3 characters. The messages are available in English and French.
Under the hood
- Add a security headers middleware, mounted on the whole router, with tests.
- Document the headers, the Google Fonts and
https:image allowances in the policy, and the advice not to duplicate them in the reverse proxy in the deployment guide.
Upgrading
- Replace the binary, or pull the new image. No schema change and no new environment variable.
- If your reverse proxy already adds the same security headers, remove them there, or browsers will receive them twice.
- The policy allows scripts from the same origin only. A custom script injected by your proxy will be blocked.
- Serve the site over HTTPS only when
ENVIRONMENT=production, because the server then asks browsers to remember HTTPS for a year.
Downloads
- RenewCMS now sends security headers on every response, including the web app and uploaded images: a Content Security Policy that forbids framing,
-
v2.8.1-beta - Docker deployment Pre-Release
released this
2026-10-05 04:56:12 +02:00 | 63 commits to main since this releaseThis release adds an official Docker deployment, so RenewCMS can run in a container instead of as a binary under systemd, and teaches Settings to guide container installs through updates.
New
Docker deployment
- A container image holds the single RenewCMS binary on Alpine, runs as a non-root user and keeps the database and uploads on one volume. A Compose file starts it with an env file and a health check.
- Images are published for amd64 and arm64 to the project registry at
git.floriansylvain.fr/fsylvain/renewcms. Prereleases carry the version tag and a movingbetatag, andlatestis only used for stable releases. - Upgrading is
docker compose pull && docker compose up -d. Your data stays on the volume. - A new Docker guide covers the quick start, backups of the data volume, the reverse proxy and TLS, reaching an SMTP server from the container, and building the image from source.
Update guidance in containers
- In a container, Settings no longer shows the browser update flow, which cannot work there. It shows the new version, the command to run on the server with a Copy button, and a link to the Docker guide.
- Download and restart are not offered in a container, and the API refuses them.
API
GET /v1/system/updatehas a newcontainerboolean. It istruewhen the server runs in a container, andcan_applyis then alwaysfalse.POST /v1/system/update/downloadandPOST /v1/system/update/restartanswer409in a container, as they already do when browser updates are not set up.
Under the hood
- Add a multi-stage Dockerfile, a Compose file with a separate build override, and tasks to build, run, pull, upgrade and publish the image. Publishing is a maintainer task that refuses to run unless the version is an exact tag on a clean tree.
- Add the container update guidance and the image publishing specifications and QA procedures to
features/.
Upgrading
- Replace the binary. No schema change and no required environment variable.
- There is one new optional variable,
RENEWCMS_CONTAINER, which defaults tofalse. The Docker image and the Compose file set it totrue. Leave it unset on a binary install. - Docker users start from the Docker guide. To move an existing binary install to Docker, copy the database and uploads into the volume and keep the same environment variables.
Known limitations
- Browser updates are not available in a container. Update by pulling a new image.
- The new container wording in Settings has no design frame yet.
Downloads
-
v2.8.0-beta - API keys and API reference Pre-Release
released this
2026-10-05 02:08:26 +02:00 | 65 commits to main since this releaseThis release adds API keys, so a script or service can use the whole API without the web interface, and turns the Integration page into a complete API reference.
New
API keys
- Create named API keys from the new API keys screen under Integration, with an optional expiry date. The full key is shown once, when you create it, and only its prefix is visible afterwards.
- Send a key as
Authorization: Bearer rcms_...to manage articles, media, users and invites, and to read settings. A key acts with exactly the rights of the user who created it, so an admin's key cannot do owner-only things. - Revoke a key at any time. Active, expired and revoked keys all stay listed with their status, creation date and last use.
- The owner sees every key and who created it. Admins see only their own.
- Each key is limited to 120 requests per minute.
- Resetting a user's password revokes all of that user's keys.
API reference
- The Integration page now documents every route a developer can call, grouped by area: articles, media, users and invites, and settings. Each endpoint lists who can call it, its request body, ready-to-copy fetch and curl examples, a sample response and its error statuses.
- A new Authentication section explains public access and API keys, and a section index lets you jump between groups.
- Routes that need a signed-in browser session are listed separately, and the page shows the real error messages the API returns.
- The Integration page and the API keys screen are available in English and French.
API
POST /v1/api-keycreates a key. It answers201with the key, shown once.GET /v1/api-keylists keys, newest first, each with astatusofactive,expiredorrevoked. For the owner, each entry also carriescreated_by_name.DELETE /v1/api-key/{id}revokes a key and answers204, also when the key is already revoked.- An invalid, unknown, expired or revoked key answers
401on every route, including public ones, and is never treated as anonymous. - A key calling a session-only route answers
403with "This action needs a real session." These routes are/v1/api-key,POST /v1/auth/verify,DELETE /v1/auth/registerand everything under/v1/system. GET /v1/auth/meaccepts an API key, so a client can check who a key belongs to.- A key beyond 120 requests per minute gets
429with aRetry-Afterheader. - Cross-origin requests now allow the
PATCHmethod.
Under the hood
- Add the API key domain, use cases, SQLite repository and an authentication middleware that runs after each token verifier. Keys are generated by a new domain interface and stored as SHA-256 hashes, never in clear.
- Add a typed API catalogue that drives the Integration page, with a test that every route has a description.
- Add the API keys and Integration specifications and QA procedures to
features/, and the API key reference todocs/api/.
Upgrading
- Replace the binary. The new API keys table is created automatically at startup. No new environment variable is required.
- Browsers calling the API from another origin can now use
PATCH. Check your CORS configuration only if you restricted methods in a reverse proxy.
Downloads
-
v2.7.0-beta - Markdown and block article bodies Pre-Release
released this
2026-10-04 19:04:12 +02:00 | 69 commits to main since this releaseThis release lets your website read article bodies as markdown, HTML or structured blocks, and fixes text alignment in the editor.
New
Article body formats
- Read an article body as markdown, HTML or a typed block array with the
formatquery parameter, so a headless frontend can render it with markdown-it, comark or its own components. - Keep text alignment in every format: markdown wraps centered, right-aligned and justified blocks in a
divwith an inline style, and the block array carries analignfield. - Keep plain text plain: text that merely looks like markdown, such as
# not a headingor- not a list, is escaped in the markdown output. - See the Integration page snippets and the article API reference for the full contract.
Improvements and fixes
- Fix the text alignment buttons in the article editor, which were disabled and showed a blocked cursor. Paragraphs and headings can be aligned left, center, right or justified again, and unaligned text stays free of inline styles.
- The web interface requests HTML explicitly when it loads, saves, publishes, unpublishes or creates an article, so the editor and the image counts behave exactly as before.
API
- Changed:
GET /v1/article/,GET /v1/article/{id},POST /v1/article/,PATCH /v1/article/{id},PATCH /v1/article/{id}/publishandPATCH /v1/article/{id}/unpublishnow returnbodyas markdown by default. Pass?format=htmlto keep the previous HTML body. - Every article object now includes
body_format(markdown,htmlorjson), which names the format ofbody. ?format=jsonon the same routes returnsbodyas a JSON array of typed blocks instead of a string.- An unsupported
formatanswers400before anything is read or written. - Storage and the request bodies of
POSTandPATCHstay HTML.
Under the hood
- Add the article body renderer port in the domain, with a markdown and JSON block adapter, and tests for every supported element.
- Update
golang.org/x/cryptoto v0.57.0 and addgolang.org/x/netas a direct dependency for HTML parsing. - Add
@tiptap/extension-text-alignto the web interface.
Upgrading
- Replace the binary. No database migration or new environment variable is required.
- Clients that read an article
bodyas HTML must add?format=htmlto their article requests, because the default is now markdown. - A markdown renderer needs raw HTML enabled for text alignment to show, for example
html: truein markdown-it.
Downloads
- Read an article body as markdown, HTML or a typed block array with the