hear what the community is talking about

Community Blogs

We’ve gathered blog posts from across the internet to highlight the many voices that make up our community. Powered by Umbraco, this space brings together diverse stories, ideas, and perspectives in one easy‑to‑explore hub. Dive in and discover what the community is creating, sharing, and talking about.

Want to add your future blog posts to the list? Submit it here!

Umbraco

PostgreSQL provider for Umbraco Search

Introducing a PostgreSQL powered search provider for Umbraco Search.

by Kenn Jacobsen

Using Umbraco.Automate to populate content from an API

Learn how to automatically populate Umbraco content from an external API using Umbraco.Automate, with a hands-on PokéAPI example and video demo.

by Owain Williams

Advanced Permissions meets Umbraco 18!

A few days ago, the final version of Umbraco 18 was released! So it was time to update my Advanced Permissions package to support Umbraco 18. This was not just a simple case of updating a few namespaces and dependencies and calling it done. The main new feature in Umbraco 18 was a perfect candidate for Advanced Permissions support, so that is exactly what I added! What is new in Umbraco 18 The Umbraco 18 release is (and I quote) "a short and sweet release". The main attraction is the new Library section and the first version of reusable Elements. Reusable Elements in the Library section are essentially Content nodes without rendering and routing. Other than that, they behave a lot like Content: you can create, delete, move them, and so on. That made them the perfect candidate for support in v18 of the Advanced Permissions package. Besides Library support, the package also has a few other improvements and additions. Let's talk about them! Library support! The first obvious new feature is the Permissions Editor for the Library. Just like with Content, you can set permissions for a User Group using the same inheritance and scoping system as the Content Permissions Editor. You'll notice that not every permission is applicable in every case, so N/A is displayed when a permission does not apply. And just like with Content, the Library also has an Access Viewer where you can see the effective permissions for a User Group or an individual User. I also added a Library Element Type Permissions Editor. Just like the Document Type Permissions Editor, you can set the insert options for Element Types for a specific User Group. With this Permissions Editor, you can control whether a User Group can create instances of an Element Type. This acts as a filter on the "create" list in the Library. And when a User has multiple User Groups, the well-established resolver will determine the effective permissions. Currently, because of a limitation in Umbraco, it is not yet possible to scope these permissions to a specific part of the Library tree like the Document Type Permissions Editor does. And of course, the Element Type Permissions Editor also has an associated Access Viewer. Redesign Because of all these new Permissions Editors and Access Viewers, the menu became harder to read at a glance. So the main menu has been redesigned around the scope of the editor and viewer. All editors and viewers in the menu are now called Permissions Editor and Access Viewer respectively. Their purpose is determined by the menu header they are under: Content, Document Type, Library, or Library Element Type. In the screenshot, you'll also notice a slight redesign of the workspaces for the editors and viewers. They now look more Umbraco native and display a one-line description of what the editor or viewer is for. And when you have not selected anything yet, it will now display a much nicer view. This will also be ported back to the Umbraco 17 version. To help or not to help In the top-right corner of each editor or viewer, you'll also notice a "learn more" button. This button gives access to more detailed documentation. It describes what the editor or viewer is for, and it links to more generic documentation explaining the concepts. Consider this a first version. It is already much better than nothing, but I think it can still be improved over time. This will also be ported back to the Umbraco 17 version. Localization support! And lastly, apart from the expanded documentation, Advanced Permissions now has localization for all languages that the Backoffice supports. Obviously, these translations are automatically generated because I am not fluent in 28 languages, so if you find an error, please let me know! All in all, I think this is a very nice and complete release. I'm happy that Advanced Permissions can support new Umbraco features as soon as they come out. You can find Advanced Permissions for Umbraco here: NuGet: https://www.nuget.org/packages/Umbraco.Community.AdvancedPermissions/ The Umbraco Marketplace: https://marketplace.umbraco.com/package/umbraco.community.advancedpermissions

by Luuk Peters

OC.Automate update

OC.Automate.LinkedIn is out of beta and OC.Automate.Bluesky and OC.Automate.Mastodon are all working on Umbraco 18

by Owain Williams

My First Codegarden Experience — A Journey from India to Copenhagen

Attending Codegarden for the first time was truly a milestone moment in my career — not just because of the event itself, but because it also became a very personal and memorable journey for me. This

by Anjumol Babu

Umbraco Automate

by Owain Williams

Umbraco Automate Workflows

by Owain Williams

I'm an Umbraco MVP for the fifth year running!

Every year, Umbraco recognises the people who have gone above and beyond with their contributions to Umbraco projects and community, and I am pleased to announce that I am honoured to be renewed as an Umbraco MVP for the fifth consecutive year. To celebrate this achievement, I also wrote an article which you can read here. Thank you, Umbraco, and the Umbraco community, for their fantastic support in making this happen once more! #h5yr

by Nurhak Kaya

Validating an Umbraco 13 to 17 Upgrade with a Sitemap Audit

How I used a small .NET console tool to crawl a sitemap, compare Umbraco 13 and 17 environments, and quickly find broken pages after an upgrade....

by Johan Reitsma

You Never Know What'll Stick

On unconventional career paths, accidental dev tools, and great people. There

by Briony McKenzie

Boost Your Workflow with Umbraco 17: Developer Productivity Hacks

As developers, we’re always searching for ways to get our work done faster. New frameworks roll out and promise to make us blazing fast, AI tools keep booming with talk about writing half our code, and if you’ve ever sat through a developer conference lately, you know there’s always a session on “How to be More Productive.” But after working with Umbraco since version 4, I have to be honest: those massive productivity leaps don’t really come from the shiny, new, revolutionary tools. It all comes down to friction. And not the dramatic, show-stopping kind. I mean those hundreds of tiny speed bumps scattered all over your day. A few extra clicks in the Umbraco backoffice. Rebuilding the same component again and again. Always double-checking a property alias. Waiting for your solution to spin up. Poring over a monster Razor file, desperately trying to recall where you put that chunk of markup. None of these feel like deal-breakers on their own, but stacked together? Suddenly, you’re delivering features at a crawl. What’s won me over with the latest Umbraco versions, especially Umbraco 17, is just how much more aligned the platform is with standard .NET development. It feels less like you’re wrestling with a quirky CMS and more like you’re just building .NET applications. There’s a real payoff: less fighting the system means more time solving real business problems.Over the years I’ve picked up quite a few habits that speed up Umbraco development and make it less painful—some are tied directly to Umbraco 17, while others are just hard-earned lessons from a decade of building websites. Here’s what’s helped the most for me.

by Dave Jonker

Extending Tiptap in Umbraco – a real world list example

If you've spent any time migrating editors or building rich-text features, you'll know Tiptap looks great on paper but sometimes feels a little... minimal compared to what TinyMCE shipped out of the box. At ilionx we've been moving a few large sites to Umbraco 17 and bumped into the same feedback: list style options. Nothing dramatic, just the ability to pick Roman numerals, different bullet shapes, that sort of thing, but enough to make editors ask for the old behavior back. So we wrote a small TypeScript extension that adds back list-style variants. It gets built to App_Plugins with Vite, plugs into the UmbTiptapExtensionApiBase and wires up extra toolbar menu items. The actual extension is tiny, it just adds a listStyleType attribute to the list nodes and renders it as inline CSS, but the surrounding bits (icons, toolbar menus, wiring) make the UX feel polished. Tiptap has solid docs on extending and creating extensions, and Umbraco documents how to register a Tiptap extension, but stitching everything together took a little trial and error. Below is the extension configuration we used. It looks like a lot for a simple feature, but a fair chunk of the files are just icons and small glue code. Let’s start with the actual extension definitions in Umbraco-package.json, where we define 4 extensions: "extensions": [ { "type": "icons", "alias": "My.Icons.TiptapListStyle", "js": "/App_Plugins/My.TiptapListStyle/list-style-icons.js" }, { "type": "tiptapExtension", "alias": "My.Tiptap.ListStyle", "api": "/App_Plugins/My.TiptapListStyle/list-style.tiptap-api.js", "meta": { "icon": "icon-ordered-list", "label": "List style type", "group": "#tiptap_extGroup_formatting" } }, { "type": "tiptapToolbarExtension", "kind": "menu", "alias": "My.Tiptap.Toolbar.OrderedList", "overwrites": "Umb.Tiptap.Toolbar.OrderedList", "api": "/App_Plugins/ My.TiptapListStyle/ordered-list-menu.tiptap-toolbar-api.js", "forExtensions": ["My.Tiptap.ListStyle", " My.Tiptap.OrderedList"], "items": [ { "label": "Numbered list", "data": null, "appearance": { "icon": "icon-ordered-list" } }, { "label": "Numbered list: i, ii, iii", "data": "lower-roman", "appearance": { "icon": "icon-list-style-lower-roman" } }, // add other style definitions here ] }, { "type": "tiptapToolbarExtension", "kind": "menu", "alias": " My.Tiptap.Toolbar.BulletList", "overwrites": "Umb.Tiptap.Toolbar.BulletList", "api": "/App_Plugins/My.TiptapListStyle/bullet-list-menu.tiptap-toolbar-api.js", "forExtensions": ["My.Tiptap.ListStyle", "Umb.Tiptap.BulletList"], "items": [ { "label": "Bullet list", "data": null, "appearance": { "icon": "icon-bulleted-list" } }, { "label": "Bullet list: circle", "data": "circle", "appearance": { "icon": "icon-list-style-bullet-circle" } }, // add other style definitions here ] } ] Here’s what we register: an icon set (a bunch of tiny SVGs showing what each list style looks like) one Tiptap extension which carries the list-style attribute two toolbar extensions that replace the ordered/bulleted list buttons with menus that expose the variants. The icon set The icons are trivial: I asked an AI to generate SVGs for different list markers and put each one in a tiny TypeScript file that exports the SVG string. /** Ordered list with lower-roman markers (i, ii, iii). */ export default `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75"><text x="1" y="7.5" font-size="4.5" fill="currentColor" stroke="none" font-family="serif" font-style="italic">i.</text><path d="M9 6h12"/><text x="1" y="13.5" font-size="4.5" fill="currentColor" stroke="none" font-family="serif" font-style="italic">ii.</text><path d="M9 12h12"/><text x="1" y="19.5" font-size="4.5" fill="currentColor" stroke="none" font-family="serif" font-style="italic">iii.</text><path d="M9 18h12"/></svg>`; And a small registry file maps the icon names to those files: export default [ { name: 'icon-list-style-lower-roman', path: () => import('./icons/svg-ordered-list-lower-roman.js'), }, //... ]; These images are purely to make the toolbar menus readable — a little UX polish so editors instantly recognise the option they’re picking. The Tiptap extension The extension itself is surprisingly compact. It adds a listStyleType attribute to the built-in orderedList and bulletList nodes and serialises it as inline list-style-type style so the HTML stays portable and renders the same in the backoffice and on the front end. import { Extension, UmbTiptapExtensionApiBase } from '@umbraco-cms/backoffice/tiptap'; /** * Extends the built-in orderedList and bulletList nodes with a `listStyleType` * attribute that maps to the CSS `list-style-type` property. * * The attribute is serialised as an inline style so the stored HTML is portable * and renders correctly both in the backoffice editor and on the front end. */ const ListStyleExtension = Extension.create({ name: 'listStyle', addGlobalAttributes() { return [ { types: ['orderedList', 'bulletList'], attributes: { listStyleType: { default: null, parseHTML: (element) => element.style.listStyleType || null, renderHTML: (attributes) => { if (!attributes['listStyleType']) return {}; return { style: `list-style-type: ${attributes['listStyleType']}` }; }, }, }, }, ]; }, }); export default class ListStyleTiptapExtensionApi extends UmbTiptapExtensionApiBase { constructor(...args: ConstructorParameters<typeof UmbTiptapExtensionApiBase>) { super(...args); this.getTiptapExtensions = () => [ListStyleExtension]; } } That’s it, the extension maps an attribute to CSS and makes sure works in both front- and backoffice. Nothing scary. The toolbar extensions Because ordered and unordered lists are separate toolbar buttons, we overwrite each one with a menu that exposes the style variants. To avoid duplicating logic we put the toolbar behaviour into a single helper and import it from the two small extension files. Each toolbar menu has to do a few things: create a list if the selection isn’t in one switch between ordered and bullet lists toggle or set a specific list-style-type and reflect the active state in the menu UI. That last bit is where the logic concentrates, the UI needs to show which style is active and allow toggling back to the default behaviour. The per-list-type extension files look like this: import { createListStyleMenuToolbarApi } from './list-style-menu-toolbar-api.js'; export default createListStyleMenuToolbarApi('bulletList'); And the shared toolbar helper contains the actual implementation and state logic: import type { Editor } from '@umbraco-cms/backoffice/tiptap'; import { UmbTiptapToolbarElementApiBase } from '@umbraco-cms/backoffice/tiptap'; type ListStyleMenuItem = { data?: string | null }; function getStyleType(item?: ListStyleMenuItem): string | null | undefined { return item?.data; } function applyListStyle( editor: Editor, listType: 'orderedList' | 'bulletList', styleType: string, ): void { if (editor.isActive(listType)) { const currentStyle = editor.getAttributes(listType)['listStyleType'] as string | null; const newStyle = currentStyle === styleType ? null : styleType; editor.chain().focus().updateAttributes(listType, { listStyleType: newStyle }).run(); return; } if (listType === 'orderedList') { editor .chain() .focus() .toggleOrderedList() .updateAttributes('orderedList', { listStyleType: styleType }) .run(); } else { editor .chain() .focus() .toggleBulletList() .updateAttributes('bulletList', { listStyleType: styleType }) .run(); } } function applyDefaultList(editor: Editor, listType: 'orderedList' | 'bulletList'): void { if (editor.isActive(listType)) { const currentStyle = editor.getAttributes(listType)['listStyleType'] as string | null; if (currentStyle) { editor.chain().focus().updateAttributes(listType, { listStyleType: null }).run(); return; } if (listType === 'orderedList') { editor.chain().focus().toggleOrderedList().run(); } else { editor.chain().focus().toggleBulletList().run(); } return; } if (listType === 'orderedList') { editor.chain().focus().toggleOrderedList().run(); } else { editor.chain().focus().toggleBulletList().run(); } } /** * Toolbar menu API for ordered/bullet list buttons with list-style-type variants. */ export function createListStyleMenuToolbarApi(listType: 'orderedList' | 'bulletList') { return class extends UmbTiptapToolbarElementApiBase { override isActive(editor?: Editor, item?: ListStyleMenuItem): boolean { if (!editor) return false; const styleType = getStyleType(item); if (styleType === undefined) { return editor.isActive(listType); } if (styleType === null) { const currentStyle = editor.getAttributes(listType)['listStyleType'] as string | null; return editor.isActive(listType) && currentStyle === null; } return editor.isActive(listType, { listStyleType: styleType }); } override execute(editor?: Editor, item?: ListStyleMenuItem): void { if (!editor) return; const styleType = getStyleType(item); if (styleType === undefined || styleType === null) { applyDefaultList(editor, listType); return; } applyListStyle(editor, listType, styleType); } }; } TODO / caveats I’m still getting comfortable with TypeScript, so this isn't a 100%-polished extension. One known quirk: clicking a custom style button after setting a style will clear the style but not fully remove the list node the way the stock ordered/bullet button does. It's a small UX edge-case and likely fixable by tweaking the toggle logic, but I haven't had time for it yet. If you want to copy this approach, the important bits are: expose a listStyleType attribute on the list nodes, serialize it in a way that works in both editor and front end (inline style is the simplest) replace the toolbar buttons with menus that set/update that attribute. Hopefully this saves you a few hours of experimentation. The overall idea is small, but the payoff for editors is surprisingly nice.

by Bernadet Goey