You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OAuth security fixes. Update as soon as you can. Behavior change: deleting an OAuth client now stops its access tokens from the next request, instead of when they expire
HTML sanitizer security fixes. Update as soon as you can. Behavior change: a link in a text or styled text field must now be a web, email or phone address, or a relative path; any other kind of link is saved as #sanitized. Add other kinds, such as whatsapp, to htmlclean.allowed_url_schemes. An unclosed tag at the end of a field is now saved as text. See HTML Sanitization
Added
ImageWorks has photo filters and duotone.filt now takes twenty-one built-in filters besides greyscale and sepia: bloom, harbor, ember, meadow, dusk, attic, pop, matte, polar, noir and ash, the duotones lagoon, sunset, fern, blush and duotone, and the extras vignette, grain, warm, cool and leak (a light leak). Chain up to 3 (ember,vignette), set a strength with filtamt (0-100), and give duotone two colors with duo, which can use the photo's own palette0 to palette4. The ImageWorks admin tool shows a thumbnail of each filter on your image, and stacks the extras on top of a look (ember,grain). Filters are free in every edition and run before watermarks, so a logo keeps its colors. Existing greyscale URLs are unchanged. On servers using GD, a filter on an image over 25 million pixels (imageworks.filterMaxPixels) is refused; on Imagick only filters with a vignette, grain or light leak are. See Photo Filters
Define your own ImageWorks filters, and ship them in extensions. A filter is a recipe of steps (sepia, saturate, overlay, vignette, gradient-map, grain, leak and more) added to imageworks.filters in tcms.php; editing a recipe re-renders its images on its own. Extensions register them with addImageFilter(), as a recipe or a PHP class, under a name that starts with their vendor (acme-noir). A new Image Filters capability lets an admin switch an extension's filters off. The ImageWorks admin tool shows each extension's filters in a row of their own, titled with the extension's name. See Your own filters
Logo watermarks can be tiled, like text watermarks.markpos: tile repeats the logo over the whole image in offset rows, so no corner is left clean to crop out. It is faint (30% opacity) unless you set markalpha, and markrepeat (1 to 12, default 4) sets how many logos run across the image. It works in a schema's watermark settings and alongside a text mark, and the ImageWorks image builder offers it as a Tile position with a count. See ImageWorks
ImageWorks draws a list of marks in one image.marks takes up to 6 layers, each with the same options as a single logo or text mark, drawn in order: a large headline over a small caption, two logos, or a photo beside a name. The flat mark and marktext parameters still work and draw first. Layered marks, including the image, color fill and wrapped text marks below, are a Pro edition feature; below Pro they are left out and the image draws without them. See Layered Marks
A mark can be an image from another record.markimage: 'authors/jane/photo' places the image from that record's image field, and .../gallery/first (or a file name) a gallery image, so a blog post's image can carry its author's photo. markshape: 'circle' cuts it round, around its focal point. A record or image that is missing leaves that layer out. See Images From Other Records
A mark can be a color fill, so text stays readable on any photo.markfill: '000000' with markalpha darkens the whole image, or a band of it sized with markh and placed with markpos. markgradient: 'bottom' (or top, left, right) fades it from solid at that edge to clear, the usual dark fade behind a caption. The color can also be one of the image's own, palette0 to palette4. Put it first in marks and the marks after it sit on top. See Color Fills
Long titles wrap instead of shrinking. Give a text mark both marktextw (the box) and marktextsize (the font size) and the text breaks between words to stay inside the box. If it still needs more than marktextlines lines (1-6, default 3), the size steps down until it fits. marktextsize can now be relative to the image, such as 6w, so a wrapped title keeps its shape at any size. marktextalign: 'center' (or right) lines the lines up. A mark with only marktextw still sits on one line, as before. See Text Size
Sitemaps can list each object's images and videos. Name the image, gallery and video properties in a collection's Sitemap card, and the sitemap lists them under each object, so search engines find the gallery images a lightbox loads and the objects behind Load More. Images are listed through ImageWorks at 1,600px, with the schema's watermark. Videos are listed with their poster or the provider's thumbnail, a title, and the page's description. See Images and Videos
Share images can carry the page's title. Write an overlay once, in Site SEO → Social Image Overlay, and every page's og:image becomes a card: its own photo with the title over it, your logo and a dark fade behind the text. The overlay is a list of ImageWorks layers, and its text takes placeholders such as ${socialTitle}, ${site} and the record's own properties (${author}). Design… opens a designer with presets, a Layers editor (drag to reorder, add a fill, text, a photo from a record or a logo, and insert a field such as the title at the cursor), a JSON tab for anything by hand, and a preview drawn on a record you pick. A collection can have an overlay of its own in its SEO settings, and Use the Image as Is on a record's SEO card shares its image without one. Only the site can produce a card, and editing the record gives the card a new address so the networks fetch the new one. Pages that hand cms.seo.head() a literal set of values, such as Stacks pages, keep the plain image. A Pro edition feature; below Pro every page shares the plain image. See Share Image Overlays
Sync Manager and tcms push can sync the Site SEO record. Site SEO is created on every install, so before this its changes never reached production: seeding skips a record the target already has. It now syncs like Pages and Data Views, from the Sync Manager or with --seo. Each site keeps its own IndexNow key and setting, and the record's images stay on each site like every other image. See Sync
Share Cards shows every page's share card before you launch.Utilities → Reporting → Share Cards renders each Site Builder page and each object of a collection the Site Builder serves through its real template, and shows its card the way a link preview does: the image, the title and the description. A card that fell back to the plain image says why, such as a layer ImageWorks refused or a template that changed the title before cms.seo.head(), and a filter shows only the cards that need a look. Drafts are included, with a badge; feeds and other pages that aren't HTML are left out. An access group can give it like any other utility. A Pro edition feature. See Share Cards
Protect pages can carry your own HTML. Two new settings in Extensions → Protect add HTML above the passcode form (a logo, a welcome line) and at the bottom of the page (a copyright line, links, a script). Both apply to every protected page, Stacks pages using protect_page() included
Changed
ImageWorks sepia has a new look. The old filter washed out to near-white on servers using Imagick and crushed the shadows on GD. It is now a warm brown that looks the same on both, so existing sepia images re-render once
Text watermarks are drawn with Intervention Image. Every mark is now drawn in one pass, where a logo and a text mark together used to take two, with a temporary file between them. Text marks may look slightly different from before (letter spacing, line spacing and outline edges), so cached images re-render once. A server without a TrueType font no longer falls back to GD's bitmap font; Roboto ships with Total CMS
A schema watermark is drawn on top of the URL's marks. Its settings used to be merged into the URL's, so a URL could set an option the schema left out, such as marktextalpha=0, and hide the mark. They are now drawn as their own layer that no URL parameter changes
Password reset keeps where the visitor started. When the login page has a redirect, its Forgot Password link passes it on, and it stays through the forgot-password form, the link in the reset email and the reset form. A whitelabel template on each step can use it, for example to show a Spanish page to someone who came from /es, and a custom reset mailer gets it as data.redirect. Only an address on your own site is kept. See URL Parameters in Password Reset
A text mark's pixel marktextsize follows dpr, like ImageWorks' other pixel sizes, so a dpr: 2 render draws the text twice as large to match the doubled image.
The MCP server runs on mcp/sdk 0.10. An MCP session now belongs to the caller that opened it: a request carrying another API key's, OAuth client's or user's session id is answered 404, as if the session did not exist. Sessions open before the update are refused once, and clients reconnect on their own. A client can now end its session with DELETE /mcp, as the MCP spec asks of a client that is done; it used to be answered 405, so sessions stayed until they expired. Browser-based MCP clients on another origin now work past initialize: they can read the session id the server returns (it was hidden from them) and send the protocol headers every later request carries. See CORS A tool call whose arguments is not an object ([] instead of {}) is refused with -32602, as the spec requires. See Security considerations
Extensions report MCP tool errors with Mcp\Server\Exception\ToolCallException. The SDK moved the class from Mcp\Exception\ToolCallException. The old name keeps working as an alias for now, so existing extensions load as before; move to the new name before a later release removes it. See Extending MCP
An MCP client is never told to reconnect for listing what its token wasn't granted. When a user unticks scopes at consent, or a token is issued without cms:admin for a user who cannot grant it, listing prompts or resources the token lacks now returns an empty list. It used to be refused with 403 insufficient_scope, which claude.ai reads as a broken connector. Reading a prompt or resource still needs the scope. See OAuth tokens and MCP scopes
OAuth keeps its short-lived state in tcms-data/.system/oauth-state/. The web server must be able to write there, as it already writes the rest of .system/. tcms oauth:gc and the daily OAuth sweep remove expired entries
Front-end libraries updated: Tiptap 3.33.1 and marked 18.2.0
Fixed
A YouTube or Vimeo embed in styled text stays where you put it. Saving used to move every allowed iframe to the end of the field
Protected image files download for signed-in users again. A protected file whose name ended in an image extension, such as photo.jpg in a depot, sent a signed-in user to the login page, and back to it after signing in, round and round. Since 3.6.3, those download links were served without the visitor's session, so they couldn't see the login
Connecting claude.ai and other OAuth clients no longer fails with "Authorization code has been revoked". The code handed out at consent was kept in the cache, so the exchange a second later failed while dev mode was on, after a cache clear, and on hosts whose APCu isn't shared between PHP workers, which made the first connect fail on many shared hosts. See Troubleshooting
MCP clients on the 2026-07-28 protocol can connect with an OAuth token. Their opening server/discover request was refused with 403 for every token, as were resources/unsubscribe and subscriptions/listen for a token with mcp:resources
The OAuth activity log names the user on token.issued, and the target on group.rejected.user_id was always empty, so an issued token looked like it belonged to nobody, and a refused create_object didn't say which collection the client asked for. See Event types
Automations sync again. A push from the Sync Manager or tcms push was refused for every automation, because only a signed-in super-admin could write automation code and a push signs in with an API key. A key with the Sync Manager permission now counts: only a super-admin can create one. Since an automation runs PHP on the server, keep that key as safe as an admin password
Data Views no longer always show as newer on the live site in the Sync Manager. Every scheduled rebuild records when it ran on the view, so a view rebuilt on production differed from your copy even when nobody had edited it. The build time and last error are now each site's own and are left out of a sync
Forms built with cms.form.builder() follow the page's language. A page that calls cms.locale.set('es') got Spanish labels from Total CMS everywhere but in its forms' own text, such as a date field's clear button, which stayed English. The forms were built without a translator
"Must be unique" errors follow the page's language. A visitor who registers with an email that's already taken now reads the refusal in the language of the page, such as Spanish, instead of English. Logs keep the English text
A select's clear button is labeled in the page's language. Its tooltip and screen reader label said "Clear selection" in English on every page. The date field's clear button was already translated
The login page no longer breaks on a crafted link. A redirect sent as a list, such as ?redirect[]=x, crashed the page. It is now treated as no redirect, and an email sent as a list no longer fills the Forgot Password and Resend Verification forms with "Array"
The JSON field can be resized in Safari. Its box had no resize grip in Safari, only in Chrome. It now has a grip of its own in the corner that works in every browser
tcms starter:export no longer asks for a credit on file uploads. A file field's image, such as the Site SEO record's SVG favicon, was named as missing a credit, but a file has no Credit section to fill in
A file a depot refuses no longer blocks saving. A file that broke a depot's upload rules, such as PDF only or a size limit, showed its error but stayed in the field with its trash button off, and the error kept every save failing until the page was reloaded. The refused file is now removed once the error shows, as image fields already do
An ImageWorks crop larger than its photo keeps its shape. ImageWorks never enlarges an image, so it used to cap the width and height separately: a 1200×630 crop of a 640×480 photo came back 640×480, not cropped at all. Both sides now shrink by the same amount, giving 640×336
Stacks Preview no longer shows a blank page when its PHP can't write to the system temp folder. With a custom PHP chosen for preview, or one with sys_temp_dir set, Preview kept its data in /var/tmp, which the sandbox refuses. It now tries each temp folder until one accepts a write, and falls back to the preview's own folder