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? Contact us here and let us know!

Umbraco

Upgrade Umbraco 13 to 17: Property Editors + Property Value Converters

This is part five in a series about common tasks you'll encounter when upgrading Umbraco 13 to 17. In this part, we’ll look at updating Property Editors and Property Value Converters. When upgrading several packages, one area that caused confusion was Property Editors. In Umbraco 17 there is a much clearer separation between the UI in the backoffice and the data handling and validation on the backend (C#). Because of this change, Umbraco introduced migrations that convert existing Data Types (which are essentially instances of a Property Editor) to the new format. In Umbraco 13 a Data Type only referenced a single editor alias, but in Umbraco 17 it contains two: an editor alias (backend) and an editor UI alias (frontend). This makes it possible, for example, to use multiple interchangeable UIs that work with the same underlying data. Why won't my property editor work anymore? After upgrading my database to Umbraco 17 I ran into a couple of issues: How does Umbraco know that it should use my newly created Property Editor UI for existing properties? Why does Models Builder suddenly return a JsonDocument instead of my custom VideoPlayerValueConverterModel? The answer lies in understanding what the migration from Umbraco 13 to 14+ actually does and what you need to do with the result. This post walks through that process. This blog explains how you can update your Property Editors to work in Umbraco 17 without having to create a custom migration. It also helps to read the Umbraco documentation on Property Editor composition. That documentation explains the different pieces that make up a Property Editor in Umbraco 17 and provides useful context for what follows. The starting point before the migration There are two different starting points for Property Editors in Umbraco 13, and each leads to a slightly different outcome during the migration to Umbraco 14+. The difference depends on whether the Property Editor is defined in a package.manifest file or in C# code. Starting point 1: manifest-only Property Editor In Umbraco 13 a Property Editor can be defined purely in a package.manifest, for example: ... "propertyEditors": [ { "alias": "proudnerds.videoplayer.editor", "name": "Video player", "icon": "icon-play", "group": "Proud Nerds", "editor": { "view": "~/App_Plugins/ProudNerds.Umbraco.VideoPlayer/umbraco.videoplayer.html" } } ] ... This is enough to create a simple data editor. The value is stored as a string in the database and there is no server-side validation. Anything can be stored as long as it fits into a string. If needed, you can still transform that value later using a Property Value Converter before it ends up in the cache. Starting point 2: Property Editor defined in code You can also define a Property Editor in C#, for example: using Umbraco.Cms.Core.PropertyEditors; namespace YourProjectName; [DataEditor( alias: "Suggestions editor", name: "Suggestions", view: "/App_Plugins/Suggestions/suggestion.html", Group = "Common", Icon = "icon-list")] public class Suggestions : DataEditor { public Suggestions(IDataValueEditorFactory dataValueEditorFactory) : base(dataValueEditorFactory) { } } Defining a Property Editor in code gives you more control over validation and how the data is stored. The resulting database records When you create a Data Type in Umbraco 13 that uses a Property Editor, the resulting database record is almost identical for both approaches. Each Data Type simply stores a property editor alias and configuration in the umbracoDataType table. If you register the editor in C#, the dbType may differ, but that detail is not important for this discussion. The data migration (13 → 14+) The difference between the two approaches becomes visible during migration. In one of the later Umbraco 13 versions a pre-migration was introduced that prepares Property Editors for upgrading to Umbraco 14+. During this step, a record is written to the umbracoKeyValue table describing how each Data Type should be migrated. For a manifest-only Property Editor, the record looks like this: [{"DataTypeId":1055, "EditorUiAlias":"proudnerds.videoplayer.editor", "EditorAlias":"Umbraco.Plain.String"}] However, if the editor was defined in C#, both aliases will be the same: [{"DataTypeId":1055, "EditorUiAlias":"proudnerds.videoplayer.editor", "EditorAlias":"proudnerds.videoplayer.editor"}] After the database upgrade to Umbraco 17, this information is used to populate the new propertyEditorAlias and propertyEditorUiAlias columns in the umbracoDataType table. For the video player editor, the result of the migration looks like this: Updating the code Once you understand what the migration produced in the database, updating the code becomes much clearer. Creating and registering the Property Editor UI With the new backoffice introduced in Umbraco 14+, the UI part of your Property Editor needs to be recreated because AngularJS is no longer supported. After creating the UI, you need to register it. I won't go into the details of building a Property Editor UI here (the documentation covers that), but the aliases you use during registration are important. Based on the migration result: alias should match PropertyEditorUiAlias propertyEditorSchemaAlias should match PropertyEditorAlias For example: export const manifests: Array<UmbExtensionManifest> = [ { type: 'propertyEditorUi', alias: "proudnerds.videoplayer.editor", name: "Proud Nerds video player property editor", js: () => import("./proud-nerds-video-property-editor-ui.element"), meta: { label: "Video player", icon: "icon-play", group: "Proud Nerds", propertyEditorSchemaAlias: "Umbraco.Plain.Json", settings: { properties: [ ... ] } } } Updating the DataEditor (if it already exists) If your editor already had a DataEditor implementation, you can continue using it, but it needs to be updated for Umbraco 17. Most of the implementation remains similar to Umbraco 13. The main difference is that several parameters have been removed from the DataEditor attribute because of the clearer separation between UI and backend logic. // Umbraco 13 [DataEditor( alias: "proudnerds.videoplayer.editor", name: "Video Player", view: "/App_Plugins/VideoPlayer/videoplayer.html", Group = "Proud Nerds", Icon = "icon-play")] public class VideoPlayerEditor : DataEditor ... // Umbraco 17 [DataEditor("proudnerds.videoplayer.editor")] public class VideoPlayerEditor : DataEditor ... Updating the Property Value Converter (if needed) If you used the manifest-only approach in Umbraco 13, there is a good chance your Property Value Converter no longer works. During migration, the EditorAlias for the video editor changed from: proudnerds.videoplayer.editor to: Umbraco.Plain.Json So if your Property Value Converter checks the editor alias, it will no longer match: public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals("proudnerds.videoplayer.editor"); The simplest fix is to check the EditorUiAlias instead: public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorUiAlias.Equals("proudnerds.videoplayer.editor"); This is not entirely semantically correct, but it is often the easiest solution without introducing additional migrations. After this, your Property Editor will work as expected again and the models builder will use your custom model instead of the generic JsonDocument. Done Once you understand how the Property Editor migration works, it becomes much easier to determine which aliases to use in umbraco-package.json and what code changes are required after upgrading. The concepts themselves are straightforward, but the migration can be confusing the first time you encounter it. Hopefully this overview helps clarify what is happening behind the scenes. EditorUiAlias or not? Earlier I mentioned that checking EditorUiAlias inside the IsConverter method of a Property Value Converter is not entirely correct from a semantic point of view. A Property Value Converter operates on the data stored in the database and converts that to something else to put in the cache. A Property Editor UI is only the interface used to edit that data. In fact, multiple UIs could theoretically exist for the same underlying editor. Because of that, checking EditorUiAlias introduces some risk. If a different UI were introduced for the same editor, your converter might no longer match the correct data. Checking EditorAlias is the safest way to guarantee you are handling the expected data structure. That said, in many real-world scenarios a Property Editor has exactly one UI and one DataEditor that always belong together. If you fully control the implementation, checking EditorUiAlias can be a pragmatic and perfectly workable solution that avoids writing additional migrations. Opinions on this tend to differ. Some developers strongly prefer to always check EditorAlias for correctness, while others consider EditorUiAlias acceptable when the editor and UI are tightly coupled. In the end, the choice depends on how strictly you want to follow the separation between UI and data.

by Luuk Peters

Auto-Updating Your GitHub README with Your Latest Blog Posts

by Owain Williams

Securing Umbraco Images with HMAC

Learn how to protect your Umbraco images from abuse by implementing HMAC authentication for image crop requests, and how to generate HMAC signatures in Next.js when using the Content Delivery API.

by Nathaniel Nunes

The Umbracian's Guide to Bristol (2026)

Are you heading to Umbraco Spark and hoping to explore Bristol while you're here? I live near Bristol and now consider myself a Spark veteran, so thought I'd share my insights!

by Joe Glombek

Running GitHub Actions .NET and Azure workflows locally with Act

Act is a fantastic tool for testing GitHub actions locally instead of pushing that 10th commit in a row called Testing GitHub Actions (again) but configuring it to work correctly can be a balancing act, so here are some tips for getting Act working with your .NET CI/CD flows.

by Joe Glombek

Introducing docs.jcdc.dev

Explore the new documentation website for all jcdcdev Umbraco packages. Built with Astro and Starlight for high performance and low carbon impact, featuring automated synchronisation across GitHub and the Umbraco Content Delivery API.

by James Carter

Battle scarred developer's guide to Umbraco v17 -Workspaces

All the code for this series of posts is available in the DoStuffWithUmbraco Repository on GitHub In the last article we got to the point where we had our own custom section, with a side menu (or "sidebarApp") and a basic menu item. but for now clicking on that menu item just shows us some loading dots 😞 What we need to do know is define a workspace. So what are workspaces ? well to lift the description directly from the umbraco docs : Workspaces provide dedicated editing environments for specific entity types in Umbraco. They create isolated areas where users can edit content, media, members, or other entities with specialized interfaces and functionality. basically menu items are assigned an 'entity type' and when you click on them Umbraco looks for the workspace that renders that entity type. entity types are really just Identifiers at this point, usually you might have in two entity types a 'root' and and 'item' - the root is as you might expect the one at the root, and the item is an item in your tree (if you have one. Item workspace For our "simple" time item, we only have a single "do-stuff-time-item" entity type, and that's the one we need to build a workspace for to show information to the user. Workspaces at their most basic have two things, a workspace context and a workspace element. Workspace Context Your workspace context is the code that controls the state and data for your workspace. here you have all your data and ways to fetch and save it. To be clear, you don't need to do this in a context, you can just fetch , store and manipulate data in the element, but as you will see in a bit, that is probably going to lead to duplication and a more complex bunch of code So without being to complicated a workspace will look something like this: export class MySampleWorkspaceContext extends UmbEditableWorkspaceContextBase<TimeSettings> implements UmbSubmittableWorkspaceContext { #settingsRepository = new DoStuffTimeSettingsRepository(this); readonly unique: Observable<string | null | undefined>; public readonly workspaceAlias = DOSTUFF_WORKSPACE_ALIAS; constructor(host: UmbControllerHost) { super(host, DOSTUFF_WORKSPACE_ALIAS); this.provideContext(DOSTUFF_WORKSPACE_CONTEXT, this); } getUnique() { return undefined; } getData() { return this.#timeSettings.value; } getEntityType() { return DOSTUFF_TIME_ITEM_ALIAS; } protected submit() { const settings = this.#timeSettings.value; if (!settings) return; await this.#settingsRepository.saveTimeSettings(settings); } } Workspaces can even be simpler than this, but here we are implementing a UmbSubmittableWorkspaceContext because we want to be able to save things that we update on our context, if you don't want to tie into other things like workspace actions, you don't need to do this, but its a relatively simple implementation, so in my opinion it's worth it. There are few things we are going to pick out in the context : ProvideContext You can see that inside the constructor for the context we are calling this.provideContext . this tells the rest of the app, that we are responsible for the DOSTUFF_WORKSPACE_CONTEXT so if anything wants to interact with this context they can. by asking for it in their own constructor. For example - a view might ask for the context like this. this.consumeContext(DOSTUFF_WORKSPACE_CONTEXT, (context) => { // do stuff with the context here. }); Workspace contexts have a scope, they will only exist while you are in the workspace, you can't call a workspace context from somewhere else or another section - if you need to do that you might need a global context, which work in a similar way they are just defined slightly differently Repositories Another thing you might have noticed about this context is we randomly just call save in a #settingsRepository and we've haven't told you what one of them is yet - we will get to them in a bit. But for now, that's just the place where the actual calls to API end points and the like are stored - it means our context doesn't need to know the inner workings of the API it can just say go get this or save that. Views So now we have registered our workspace and we have a workspace context, you will notice, we still have nothing to show to our user! So now its time to fix that, and present them with something. You define what things show in a workspace via workspaceView elements. again we define these in a manifest. type: "workspaceView", alias: "DoStuff.DefaultWorkspaceView", name: "DoStuff Default Workspace View", js: () => import("./default-workspace-view.element.js"), weight: 500, meta: { label: "#doStuff_defaultWorkspaceViewName", pathname: "default", icon: "icon-alarm-clock", }, conditions: [ { alias: "Umb.Condition.WorkspaceAlias", match: DOSTUFF_WORKSPACE_ALIAS, }, ], }, So the things to note : the js entry points the file that will render the element the meta data defines the name and icon for the view the conditions make sure our view only shows up on our workspace. View element The view element is a UmbLitElement that renders what you want to show the user @customElement("do-stuff-default-workspace-view-element") export class DoStuffDefaultWorkspaceViewElement extends UmbLitElement { override render() { return html`<umb-body-layout> <div class="layout"> <uui-box .headline=${this.localize.term("doStuff_defaultWorkspaceTitle")} > <h1>Hello Time</h1> </uui-box> </div> </umb-body-layout>`; } } With this in place we get something to show the user ! Multiple views, and icons. So we defined that icon, and name, but it's not anywhere on the screen, what's up with that?. Well the icon and name are used when we have multiple views in a workspace. so if we add another workspaceView manifest to the workspace { type: "workspaceView", alias: "DoStuff.SettingsWorkspaceView", name: "DoStuff Settings Workspace View", js: () => import("./settings-workspace-view.element.js"), weight: 200, meta: { label: "#doStuff_settingsWorkspaceViewName", pathname: "settings", icon: "icon-settings", }, conditions: [ { alias: "Umb.Condition.WorkspaceAlias", match: DOSTUFF_WORKSPACE_ALIAS, }, ], }, We get our 'tabs' Summarry This gets us the skeleton of our workspace up and showing something to the user - there is much more to workspaces , and we will go into them in some later posts, but for now, we have a section, a menu and some workspaces. so 🎉

by Kevin Jump

How I used Umbraco.AI for free

by Owain Williams

Umbraco 17 Backoffice Extensions for Beginners: TypeScript + Lit + Vite

by Girish Sasikumar

Umbraco testing examples now also for Umbraco 17

I updated my testing examples repository to the latest Umbraco version. D-Inventor / automated-testing-in-umbraco A working example of integration- and unittests with Umbraco. A demonstration of various concepts for testing your Umbraco website Umbraco 17 automated testing setup This project is a fully functioning setup for automated testing with Umbraco 17. You can use this project as a reference or starting point to get started with testing on your Umbraco website. The tests are set up with Test Driven Development (TDD) in mind. Tools The most important tools that are used in the automated tests are as follows: Name Description xUnit v3 The testing framework. You can use any testing framework that you like though NSubstitute Library for mocking. Any mocking library will work. This project doesn't do extensive mocking, but for example IPublishedValueFallback is a mandatory parameter for any published content item, even if you don't actually use it. It's just convenient to insert a mock. Test Containers Automatically creates docker containers while running tests. It is used to create an empty SQL Server database that is automatically cleaned up after testing. … View on GitHub Though not much has changed, here are the most notable differences compared to the Umbraco 16 version Database initialization The SqlServerDatabase resource no longer creates a second database inside the SqlServer test container. While Umbraco 16 would happily create a new database on boot if it didn't exist yet, Umbraco 17 does not and will throw connection errors. For your regular use of Umbraco, this change makes no significant difference. If you also use EF Core, you may need to pay some extra attention here! A base URL for integration tests The WebsiteFixture will now set a fixed base URL when creating a backoffice httpclient. If you need to use a different domain, this base URL can be adapted, but what is important is that the URL uses https protocol. Compared to Umbraco 16, Umbraco 17 requires https to backoffice endpoints by default and requests using http are rejected by default. That's at least what the error would indicate. If you want to see all the changes, you can check out this commit: https://github.com/D-Inventor/automated-testing-in-umbraco/commit/573ef96a155c390ba3d4383bf555f9ff5723b3b4 An additional note I have given some attention to the example website under test in this repository. The homepage at least has a slightly more fancy template, but it's all static content. I haven't yet taken the time to really tie it all together, and I wanted to do so using Test Driven Development, which is the main focus of this example repository. I noticed that the version of playwright for C# doesn't actually let you compare screenshots!? This was a mild surprise and disappointment, because that means I can't actually use snapshot testing to ensure my template doesn't accidentally change when I implement the logic. The Javascript version of playwright does seem to support it, so I guess for better end-to-end testing, we'd need to create an equivalent management API client and scenario builder. Oof! That's all that's all I wanted to share! Hope you check out the repository, give it a star and let me know if the testing examples have been helpful to you or not! Thank you for reading 😊

by Dennis Heutinck

Battle scarred developer's guide to Umbraco v17 - Sections

All the code for this series of posts is available in the DoStuffWithUmbraco Repository on GitHub 1. Sections (see DoStuffWithUmbracoRepo : Sections) A section in umbraco is something accessed from the top bar navigation menu. Its quite simple to add a new section - all you need is the manifest. const sectionManifest: UmbExtensionManifest = { type: "section", alias: DOSTUFF_SECTION_ALIAS, name: "DoStuff Section", weight: 10, meta: { label: "#doStuff_sectionName", pathname: "do-stuff", }, }; Constants Here we have used a constant for the alias, because its often true you will need to reference the section alias in other places on your site. The alias is defined in another file. export const DOSTUFF_SECTION_ALIAS = "DoStuff.Section"; and this replaces the string value in the manifest. Localization You might also notice that the label value starts with a '#', this tells umbraco that we want to use a localize value for the label. we will cover them a bit later, but for now just to so you know you can just put a string in for the label value if you want 2. Dashboard (see DoStuffWithUmbraco repo : Dashboards) At this point your section is quite empty, but you can add a dashboard quite easily to get some content in there. Dashboards are pages that sit at the top level of a section, almost all the sections in umbraco already have dashboards (Content has news, and redirect, settings has, examine, health checks and profiling to name a few). you can add your own dashboard to your own sections or existing sections as you choose. const dashboardManifest: UmbExtensionManifest = { type: "dashboard", alias: "DoStuff.Dashboard", name: "DoStuff Dashboard", js: () => import("./dashboard.element.js"), meta: { label: "#DoStuff_DashboardName", pathname: "do-stuff-dashboard", }, conditions: [ { alias: "Umb.Condition.SectionAlias", match: DOSTUFF_SECTION_ALIAS, }, ], }; Here we define the alias and element we want to use for our dashboard, and the condition determines where it lives. there are all sorts of conditions in umbraco, but here we are saying if the section alias is that of our custom section then we are happy. If we didn't have a condition the dashboard would appear in all sections Again note the use of a constant for the section alias_ Dashboards are WebComponents, they are probibly the simplest ones in umbraco you don't have to inherit anything (if you don't want to) - there are no contexts, or stores or tree's required, you element can just render some HTML. @customElement("do-stuff-dashboard-element") export class DoStuffDashboardElement extends UmbLitElement { override render() { return html`<umb-body-layout .headline=${this.localize.term("doStuff_dashboardTitle")} > <uui-box> <umb-localize key="doStuff_dashboardIntro"></umb-localize> </uui-box> </umb-body-layout>`; } } export default DoStuffDashboardElement; Note : We do inherit from UmbLitElement as opposed to LitElement, this gives you access to the localization helpers amongst other things, so it's worth doing, even though you don't have to. 3. Sidebar App. You have probibly notices that almost all existing sections in umbraco have some form of menu or tree down the left hand side. In Umbraco speak this is a "SidebarApp" and it can contain almost anything! - but usually its menu items and trees. A sidebar is registered in a manifest file. const sidebarManifest: UmbExtensionManifest = { type: "sectionSidebarApp", kind: "menu", alias: "DoStuff.SectionSidebarApp", name: "DoStuff Section Sidebar App", meta: { label: "#doStuff_sidebarStaticAppName", menu: "DoStuff.Static.Menu", }, conditions: [ { alias: "Umb.Condition.SectionAlias", match: DOSTUFF_SECTION_ALIAS, }, ], }; On its own a sidebar app is quite empty. So we need to add some menus and items. 4. Menus Within a standard sidebar you can have a number of 'menus' these are the sections you see in the settings section, "Structure", "Templating" and "Advanced" are Menus to add your own, we have a manifest. const menuManifest: UmbExtensionManifest = { type: "menu", alias: "DoStuff.Static.Menu", name: "DoStuff Static Menu", }; but with out any menu items you won't see much , so lets add a menu item. const timeItemManifest: UmbExtensionManifest = { type: "menuItem", alias: "DoStuff.TimeItem", name: "DoStuff Time Item", weight: 10, meta: { label: "#doStuff_timeItemName", icon: "icon-time", entityType: DOSTUFF_TIME_ITEM_ALIAS, menus: ["DoStuff.Static.Menu"], }, }; You can see how we assign the menu item to the menu, and the menu is actually assigned in the section definition. Don't worry to much about the entityType just yet, its going to get funky when we talk about workspaces. but for now lets just bask in the menu glory. All the client code in this article is available in the DoStuffWithUmbraco Client repo

by Kevin Jump

Battle scarred developer's guide to Umbraco v17 - Entry Points

All the code for this series of posts is available in the DoStuffWithUmbraco Repository on GitHub So now that bundles have replaced entry points as the place to integrate your Umbraco extension, do we still need entry points? Yes, i most cases you will still need an entry point, to initialize things like the authentication for your client to talk to the server. or if you are feeling really mischievous to unload other entries from the umbraco registry ! Register your entry point via a manifest. all the extension template will do this one for you, but to show the process export const manifests: Array<UmbExtensionManifest> = [ { name: "Do Stuff Client Entrypoint", alias: "DoStuff.Client.Entrypoint", type: "backofficeEntryPoint", js: () => import("./entrypoint.js"), }, ]; this is then imported and registered via your bundle.manifest.ts file. Auth. The examples that you get with the templates contain most of the code you will need here for authentication. _host.consumeContext(UMB_AUTH_CONTEXT, async (authContext) => { // Get the token info from Umbraco const config = authContext?.getOpenApiConfiguration(); client.setConfig({ auth: config?.token ?? undefined, baseUrl: config?.base ?? "", credentials: config?.credentials ?? "same-origin", }); }); }; As we've said in another post we like to add a bit - which keeps the token fresh for each request. // client interceptor will get the latest token for the auth // context for a request, so if the token has been refreshed // since we first got it, we'll still have a valid token. client.interceptors.request.use(async (request, _options) => { const token = await authContext?.getLatestToken(); request.headers.set("Authorization", `Bearer ${token}`); return request; }); honesty - i am not 100% sure this extra bit is in fact needed - it's something we might revisit. Unregistering things! So your onInit method runs when your extension is loaded, and you can if you want start to manipulate Umbraco's register, you could for example remove the welcome dashboard this way. extensionRegistry.unregister('Umb.Dashboard.UmbracoNews'); Be carefull! removing things from the register might well make bits of the umbraco backoffice unstable.

by Kevin Jump