Umbraco CMS
CloudHeartcoreDXPMarketplace
16.latest
16.latest
  • Umbraco CMS Documentation
  • Legacy Documentation
    • Our Umbraco
    • GitHub
  • Release Notes
  • Contribute
  • Sustainability Best Practices
  • Fundamentals
    • Get to know Umbraco
    • Setup
      • Requirements
      • Installation
        • Install using .NET CLI
        • Running Umbraco in Docker using Docker Compose
        • Install using Visual Studio
        • Local IIS With Umbraco
        • Install using Visual Studio Code
        • Installing Nightly Builds
        • Running Umbraco on Linux/macOS
        • Unattended Installs
      • Upgrade your project
        • Version Specific Upgrades
          • Upgrade from Umbraco 8 to the latest version
          • Migrate content to Umbraco 15
          • Migrate content to Umbraco 8
          • Minor upgrades for Umbraco 8
          • Upgrade to Umbraco 7
          • Minor upgrades for Umbraco 7
      • Server setup
        • Running Umbraco On Azure Web Apps
        • Hosting Umbraco in IIS
        • File And Folder Permissions
        • Runtime Modes
        • Umbraco in Load Balanced Environments
          • Load Balancing Azure Web Apps
          • Standalone File System
          • Advanced Techniques With Flexible Load Balancing
          • Logging With Load Balancing
    • Backoffice
      • Sections
      • Property Editors
        • Built-in Property Editors
          • Checkbox List
          • Collection
          • Color Picker
          • Content Picker
          • Document Picker
          • DateTime
          • Date
          • Decimal
          • Email Address
          • Eye Dropper Color Picker
          • File Upload
          • Image Cropper
          • Label
          • Markdown Editor
          • Media Picker
          • Member Group Picker
          • Member Picker
          • Multi Url Picker
          • Repeatable Textstrings
          • Numeric
          • Radiobutton List
          • Slider
          • Tags
          • Textarea
          • Textbox
          • Toggle
          • User Picker
          • Block Editors
            • Block Grid
            • Block List
            • Block Level Variance
          • Dropdown
          • Rich Text Editor
            • Configuration
            • Extensions
            • Blocks
            • Style Menu
      • Login
      • Document Blueprints
      • Sidebar
      • Log Viewer
      • Language Variants
      • Settings Dashboards
    • Data
      • Defining Content
        • Default Document Types
        • Document Type Localization
      • Creating Media
        • Default Data/Media Types
      • Members
      • Data Types
        • Default Data Types
      • Scheduled Publishing
      • Using Tabs
      • Users
        • API Users
      • Relations
      • Dictionary Items
      • Content Version Cleanup
    • Design
      • Templates
        • Basic Razor Syntax
        • Razor Cheatsheet
      • Rendering Content
      • Rendering Media
      • Partial Views
      • Stylesheets And JavaScript
    • Code
      • Service APIs
      • Subscribing To Notifications
      • Creating Forms
      • Debugging
        • Logging
      • Source Control
  • Implementation
    • Learn how Umbraco works
    • Routing
      • Controller & Action Selection
      • Execute Request
      • Request Pipeline
    • Custom Routing
      • Adding a hub with SignalR and Umbraco
    • Controllers
    • Data Persistence (CRUD)
    • Composing
    • Integration Testing
    • Nullable Reference Types
    • Services and Helpers
      • Circular Dependencies
    • Unit Testing
  • Customizing Backoffice
    • Overview
    • Setup Your Development Environment
      • Umbraco Extension Template
      • Vite Package Setup
    • Extensions Overview
      • Extension Registry
        • Extension Registration
        • Extension Manifest
        • Replace, Exclude or Unregister
      • Extension Types
        • Sections
          • Sections
          • Section Sidebar
          • Section View
        • Workspaces
          • Workspace Actions
          • Workspace Context
          • Workspace Views
        • Menu
        • Header Apps
        • Icons
        • Block Custom View
        • Bundle
        • Kind
        • App Entry Point
        • Backoffice Entry Point
        • Extension Conditions
        • Dashboards
        • Entity Actions
        • Entity Bulk Actions
        • Entity Create Option Action
        • Property Value Preset
        • Trees
        • Global Context
        • Localization
        • Modals
          • Confirm Dialog
          • Custom Modals
          • Modal Route Registration
      • Extension Kind
      • Extension Conditions
      • Custom Extension types
    • Foundation
      • Fetching Data
        • Fetch API
        • Umbraco HTTP Client
        • Executing Requests
        • Custom Generated Client
      • Terminology
      • Umbraco Controller
        • Write your own controller
      • Umbraco Element
      • Lit Element
      • Context API
        • Consume a Context
        • Provide a Context
      • Repositories
      • States
      • UI Sorting
      • Routes
      • Backoffice Localization
    • Contexts
      • Property Dataset Context
    • Sections & Trees
    • Property Level UI Permissions
    • Icons
    • Searchable Trees (ISearchableTree)
    • Property Editors
      • Property Editors Composition
        • Property Editor Schema
        • Property Editor UI
      • Property Value Converters
      • Property Actions
      • Integrate Property Editors
      • Tracking References
      • Content Picker Value Converter Example
      • Property Dataset
      • Integrate Validation
    • Workspaces
    • Umbraco Package
    • UI Library
  • Extending Umbraco
    • Overview
    • Health Check
      • Health Check Guides
        • Click-Jacking Protection
        • Content Content Security Policy (CSP)
        • Content/MIME Sniffing Protection
        • Cross-site scripting Protection (X-XSS-Protection header)
        • Debug Compilation Mode
        • Excessive Headers
        • Fixed Application Url
        • Folder & File Permissions
        • HTTPS Configuration
        • Notification Email Settings
        • SMTP
        • Strict-Transport-Security Header
    • Language Files & Localization
      • .NET Localization
    • Backoffice Search
    • Creating a Custom Database Table
    • Creating a Custom Seed Key Provider
    • Embedded Media Providers
    • Custom File Systems (IFileSystem)
      • Using Azure Blob Storage for Media and ImageSharp Cache
    • Configuring Azure Key Vault
    • Packages
      • Creating a Package
      • Language file for packages
      • Listing a Package on the Umbraco Marketplace
      • Good practice and defaults
      • Packages on Umbraco Cloud
      • Installing and Uninstalling Packages
      • Maintaining packages
      • Create accessible Umbraco packages
      • Example Package Repository
  • Reference
    • Dive into the code
    • Configuration
      • Basic Authentication Settings
      • Connection strings settings
      • Content Dashboard Settings
      • Content Settings
      • Data Types Settings
      • Debug settings
      • Examine settings
      • Exception filter settings
      • FileSystemProviders Configuration
      • Global Settings
      • Health checks
      • Hosting settings
      • Imaging settings
      • Indexing settings
      • Install Default Data Settings
      • Logging settings
      • Maximum Upload Size Settings
      • Models builder settings
      • Cache Settings
      • Package Migration
      • Plugins settings
      • Request handler settings
      • Runtime settings
      • Security Settings
      • Serilog settings
      • Type finder settings
      • Unattended
      • Web routing
    • Templating
      • Models Builder
        • Introduction
        • Configuration
        • Builder Modes
        • Understand and Extend
        • Using Interfaces
        • Tips and Tricks
      • Working with MVC
        • Working with MVC Views in Umbraco
        • View/Razor Examples
        • Using MVC Partial Views in Umbraco
        • Using View Components in Umbraco
        • Querying & Traversal
        • Creating Forms
      • Macros
    • Querying & Models
      • IMemberManager
      • IPublishedContentQuery
      • ITagQuery
      • UDI Identifiers
      • UmbracoContext helper
      • UmbracoHelper
      • IPublishedContent
        • IPublishedContent Collections
        • IPublishedContent IsHelpers
        • IPublishedContent Property Access & Extension Methods
    • Routing & Controllers
      • Custom MVC controllers (Umbraco Route Hijacking)
      • Custom MVC Routes
      • Custom Middleware
      • URL Rewrites in Umbraco
      • Special Property Type aliases for routing
      • URL Redirect Management
      • Routing in Umbraco
        • FindPublishedContentAndTemplate()
        • IContentFinder
        • Inbound request pipeline
        • Outbound request pipeline
        • Published Content Request Preparation
      • Surface controllers
        • Surface controller actions
      • Umbraco API Controllers
        • Porting old Umbraco API Controllers
    • Content Delivery API
      • Custom property editors support
      • Extension API for querying
      • Media Delivery API
      • Protected content in the Delivery API
        • Server to server access
      • Output caching
      • Property expansion and limiting
      • Additional preview environments support
    • Webhooks
      • Expanding Webhook Events
    • API versioning and OpenAPI
    • Searching
      • Examine
        • Examine Management
        • Examine Manager
        • Custom indexing
        • PDF indexes and multisearchers
        • Quick-start
    • Using Notifications
      • Notification Handler
      • CacheRefresher Notifications Example
      • ContentService Notifications Example
      • Creating And Publishing Notifications
      • Determining if an entity is new
      • MediaService Notifications Example
      • MemberService Notifications Example
      • Sending Allowed Children Notification
      • Umbraco Application Lifetime Notifications
      • EditorModel Notifications
        • Customizing the "Links" box
      • Hot vs. cold restarts
    • Inversion of Control / Dependency injection
    • Management
      • Using Umbraco services
        • Consent Service
        • Media Service
        • Relation Service
        • Content Service
        • Content Type Service
        • Localization Service
        • User Service
    • Plugins
      • Creating Resolvers
      • Finding types
    • Cache & Distributed Cache
      • Cache Seeding
      • Accessing the cache
      • ICacheRefresher
      • IServerMessenger
      • Getting/Adding/Updating/Inserting Into Cache
      • Examples
        • Working with caching
    • Response Caching
    • Security
      • API rate limiting
      • BackOfficeUserManager and Events
      • Cookies
      • Replacing the basic username/password check
      • External login providers
      • Locking of Users and password reset
      • Reset admin password
      • Umbraco Security Hardening
      • Umbraco Security Settings
      • Sensitive data
      • Sanitizing the Rich Text Editor
      • Setup Umbraco for a FIPS Compliant Server
      • HTTPS
      • Two-factor Authentication
      • Server-side file validation
    • Scheduling
    • Common Pitfalls & Anti-Patterns
    • API Documentation
    • Debugging with SourceLink
    • Language Variation
    • UmbracoMapper
    • Distributed Locks
    • Management API
      • External Access
      • Setup OAuth using Postman
    • Custom Swagger API
    • Umbraco Flavored Markdown
    • Content Type Filters
  • Tutorials
    • Overview
    • Creating a Basic Website
      • Getting Started
      • Document Types
      • Creating Your First Template
      • CSS and Images
      • Displaying the Document Type Properties
      • Creating a Master Template
      • Creating Pages and Using the Master Template
      • Setting the Navigation Menu
      • Articles and Article Items
      • Adding Language Variants
      • Conclusions
    • Creating your First Extension
    • Creating a Custom Dashboard
      • Adding localization to the dashboard
      • Adding functionality to the Dashboard
      • Using Umbraco UI library in the Dashboard
    • Creating a Property Editor
      • Adding configuration to a Property Editor
      • Integrating context with a Property Editor
      • Custom value conversion for rendering
      • Adding server-side validation
        • Default Property Editor Schema aliases
    • Creating a Multilingual Site
    • Add Google Authentication (Users)
    • Add Microsoft Entra ID authentication (Members)
    • Creating Custom Database Tables with Entity Framework
    • Migrating Macros
    • The Starter Kit
      • Install the Starter Kit
      • Lessons
        • Customize the Starter Kit
        • Add a Blog Post Publication Date
          • Add a Blog Post Publication Date
          • Add a Blog Post Publication Date
        • Add Open Graph
          • Add Open Graph - Step 1
          • Add Open Graph - Step 2
          • Add Open Graph - Step 3
          • Add Open Graph - Step 4
          • Add Open Graph - Summary
        • Ask For Help and Join the Community
    • Editor's Manual
      • Getting Started
        • Logging In and Out
        • Umbraco Interface
        • Creating, Saving and Publishing Content Options
        • Finding Content
        • Editing Existing Content
        • Sorting Pages
        • Moving a Page
        • Copying a Page
        • Deleting and Restoring Pages
      • Working with Rich Text Editor
      • Version Management
        • Comparing Versions
        • Rollback to a Previous Version
      • Media Management
        • Working with Folders
        • Working with Media Types
        • Cropping Images
      • Tips & Tricks
        • Refreshing the Tree View
        • Audit Trail
        • Notifications
        • Preview Pane Responsive View
        • Session Timeout
    • Multisite Setup
    • Member Registration and Login
    • Custom Views for Block List
    • Connecting Umbraco Forms and Zapier
    • Creating an XML Sitemap
    • Implement Custom Error Pages
    • Create a custom maintenance page
    • Creating a backoffice API
      • Documenting your controllers
      • Adding a custom Swagger document
      • Versioning your API
      • Polymorphic output in the Management API
      • Umbraco schema and operation IDs
      • Access policies
    • Extending the Help Menu
Powered by GitBook
On this page
  • Creating the class
  • Retrieving all items
  • Retrieving a single item
  • Creating a new item
  • Updating an item
  • Deleting an item
  • Full implementation

Was this helpful?

Edit on GitHub
Export as PDF
  1. Tutorials

Creating a backoffice API

In this article, you will learn how to create your own API in the Umbraco backoffice. This is a great way to extend the functionality of the Umbraco backoffice and create custom endpoints for your use.

The end result for this article is to create a custom API called "My item API" in the Management API found at /umbraco/swagger/.

The Umbraco Backoffice API is also known as the Management API. Thus, a Backoffice API Controller is often referred to as a Management API Controller.

Creating the class

To create a custom API, you need to create a class that inherits from Umbraco.Cms.Web.BackOffice.Controllers.ManagementApiControllerBase.

The ManagementApiControllerBase serves as the foundation for your custom API class. It provides essential functionalities and utilities required for managing APIs within the Umbraco backoffice environment.

We also use the VersionedApiBackOfficeRoute attribute to define the route for our API. This attribute takes a string parameter that defines the route for the API. This route will be appended to the base route for the backoffice API.

MyItemApiController.cs
[VersionedApiBackOfficeRoute("my/item")]
[ApiExplorerSettings(GroupName = "My item API")]
public class MyItemApiController : ManagementApiControllerBase
{
}

Retrieving all items

Now that we have our class set up, we can add an action to get all items. We will use the HttpGet attribute to define the HTTP method and route for the action.

The AllItems field is an in-memory list of items to simulate the use of a repository. We use the skip & take parameters here, so users of this endpoint can implement paging. We also use the PagedViewModel to return the given items (10 by default), and then the total number of items.

MyItemApiController.cs
[HttpGet]
public IActionResult GetAllItems(int skip = 0, int take = 10)
    => Ok(
        new PagedViewModel<MyItem>
        {
            Items = AllItems.Skip(skip).Take(take),
            Total = AllItems.Count
        }
    );

AllItems is a local list:

MyItemApiController.cs
private static readonly List<MyItem> AllItems = Enumerable.Range(1, 100)
        .Select(i => new MyItem($"My Item #{i}"))
        .ToList();

The model for MyItem is a basic class with an Id and a Value property.

MyItem.cs
public class MyItem(string value)
{
    public Guid Id { get; } = Guid.NewGuid();

    public string Value { get; set; } = value;
}

Retrieving a single item

We can now create some logic to return a response based on the ID. The route parameter {id:guid} specifies that the id parameter should be a GUID. Here we're creating a local in-memory list of items and returning the item with the matching ID.

To note here is the use of the OperationStatusResult method. This method allows you to return a response with a status code and a body. This is useful for returning error responses with additional information.

The method also needs an enum operationStatus, as it will be attached to the response. This is a basic example, however, this OperationStatus would be returned from your service layer, based on the error in the service layer method.

MyItemApiController.cs
[HttpGet("{id:guid}")]
public IActionResult GetItem(Guid id)
{
    MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);

    return item is not null
        ? Ok(item)
        : OperationStatusResult(
            MyItemOperationStatus.NotFound,
            builder => NotFound(
                builder
                    .WithTitle("That thing wasn't there")
                    .WithDetail("Maybe look for something else?")
                    .Build()
            )
        );
}
MyItemOperationStatus.cs
public enum MyItemOperationStatus
{
    NotFound,
    InvalidValue,
    DuplicateValue
}

Creating a new item

We can now add an action to create a new item. We use the HttpPost attribute to define the HTTP method and route for the action. Here we can see some validation logic.

If the value does not start with "New", we return a BadRequest response with an error message. This highlights why we use the OperationStatusResult method. We can return a detailed response.

We also use CreatedAtId<MyItemApiController>, a helper method to create a response with a 201 Created status code and a Location header.

MyItemApiController.cs
[HttpPost]
public IActionResult CreateItem(string value)
{
    if (value.StartsWith("New") is false)
    {
        return OperationStatusResult(
            MyItemOperationStatus.InvalidValue,
            builder => BadRequest(
                builder
                    .WithTitle("That was invalid")
                    .Build()
            )
        );
    }

    if (AllItems.Any(item => item.Value.InvariantEquals(value)))
    {
        return OperationStatusResult(
            MyItemOperationStatus.DuplicateValue,
            builder => BadRequest(
                builder
                    .WithTitle("No duplicate values, please.")
                    .Build()
            )
        );
    }

    var newItem = new MyItem(value);
    AllItems.Add(newItem);
    return CreatedAtId<MyItemApiController>(
        ctrl => nameof(ctrl.GetItem),
        newItem.Id
    );
}

Updating an item

Now we can add an action to update an item. We can use the HttpPut attribute to define the HTTP method and route for the action.

MyItemApiController.cs
[HttpPut("{id:guid}")]
public IActionResult UpdateItem(Guid id, string value)
{
    MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);
    if (item is null)
    {
        return OperationStatusResult(
            MyItemOperationStatus.NotFound,
            builder => NotFound(
                builder
                    .WithTitle("That thing wasn't there")
                    .WithDetail("Maybe look for something else?")
                    .Build()
            )
        );
    }

    if (AllItems.Any(i => i.Value.InvariantEquals(value)))
    {
        return OperationStatusResult(
            MyItemOperationStatus.DuplicateValue,
            builder => BadRequest(
                builder
                    .WithTitle("You have been DUPED")
                    .Build()
            )
        );
    }

    item.Value = value;
    return Ok();
}

Deleting an item

Finally, we can add an action to delete an item. We can use the HttpDelete attribute to define the HTTP method and route for the action.

MyItemApiController.cs
[HttpDelete("{id:guid}")]
public IActionResult DeleteItem(Guid id)
{
    MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);

    if (item is null)
    {
        return OperationStatusResult(
            MyItemOperationStatus.NotFound,
            builder => NotFound(
                builder
                    .WithTitle("That thing wasn't there")
                    .WithDetail("Maybe look for something else?")
                    .Build()
            )
        );
    }

    AllItems.Remove(item);
    return Ok();
}

Now we have created the custom API for our Umbraco project. Below you can see the full example of the implementation.

Full implementation

MyItemApiController.cs
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Controllers;
using Umbraco.Cms.Api.Management.Routing;

namespace UmbracoDocs.Samples;

[VersionedApiBackOfficeRoute("my/item")]
[ApiExplorerSettings(GroupName = "My item API")]
public class MyItemApiController : ManagementApiControllerBase
{
    private static readonly List<MyItem> AllItems = Enumerable.Range(1, 100)
        .Select(i => new MyItem($"My Item #{i}"))
        .ToList();

    [HttpGet]
    public IActionResult GetAllItems(int skip = 0, int take = 10)
        => Ok(
            new PagedViewModel<MyItem>
            {
                Items = AllItems.Skip(skip).Take(take),
                Total = AllItems.Count
            }
        );

    [HttpGet("{id:guid}")]
    public IActionResult GetItem(Guid id)
    {
        MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);

        return item is not null
            ? Ok(item)
            : OperationStatusResult(
                MyItemOperationStatus.NotFound,
                builder => NotFound(
                    builder
                        .WithTitle("That thing wasn't there")
                        .WithDetail("Maybe look for something else?")
                        .Build()
                )
            );
    }

    [HttpPost]
    public IActionResult CreateItem(string value)
    {
        if (value.StartsWith("New") is false)
        {
            return OperationStatusResult(
                MyItemOperationStatus.InvalidValue,
                builder => BadRequest(
                    builder
                        .WithTitle("That was invalid")
                        .Build()
                )
            );
        }

        if (AllItems.Any(item => item.Value.InvariantEquals(value)))
        {
            return OperationStatusResult(
                MyItemOperationStatus.DuplicateValue,
                builder => BadRequest(
                    builder
                        .WithTitle("No duplicate values, please.")
                        .Build()
                )
            );
        }

        var newItem = new MyItem(value);
        AllItems.Add(newItem);
        return CreatedAtId<MyItemApiController>(
            ctrl => nameof(ctrl.GetItem),
            newItem.Id
        );
    }

    [HttpPut("{id:guid}")]
    public IActionResult UpdateItem(Guid id, string value)
    {
        MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);
        if (item is null)
        {
            return OperationStatusResult(
                MyItemOperationStatus.NotFound,
                builder => NotFound(
                    builder
                        .WithTitle("That thing wasn't there")
                        .WithDetail("Maybe look for something else?")
                        .Build()
                )
            );
        }

        if (AllItems.Any(i => i.Value.InvariantEquals(value)))
        {
            return OperationStatusResult(
                MyItemOperationStatus.DuplicateValue,
                builder => BadRequest(
                    builder
                        .WithTitle("You have been DUPED")
                        .Build()
                )
            );
        }

        item.Value = value;
        return Ok();
    }

    [HttpDelete("{id:guid}")]
    public IActionResult DeleteItem(Guid id)
    {
        MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);

        if (item is null)
        {
            return OperationStatusResult(
                MyItemOperationStatus.NotFound,
                builder => NotFound(
                    builder
                        .WithTitle("That thing wasn't there")
                        .WithDetail("Maybe look for something else?")
                        .Build()
                )
            );
        }

        AllItems.Remove(item);
        return Ok();
    }
}

public class MyItem(string value)
{
    public Guid Id { get; } = Guid.NewGuid();

    public string Value { get; set; } = value;
}

public enum MyItemOperationStatus
{
    NotFound,
    InvalidValue,
    DuplicateValue
}
PreviousCreate a custom maintenance pageNextDocumenting your controllers

Last updated 2 months ago

Was this helpful?