Development of Custom Umbraco Package
Umbraco package is NuGet package that extends CMS: adds new property types, dashboards, backoffice sections, Composers, migrations, API endpoints. Distributed via NuGet or Our.Umbraco.Org. Architecturally it's regular .NET library with Composer registration.
Package Structure
MyPackage/
├── MyPackage.Core/
│ ├── Composers/
│ │ └── MyPackageComposer.cs
│ ├── Services/
│ │ ├── IMyService.cs
│ │ └── MyService.cs
│ ├── Models/
│ ├── Migrations/
│ │ └── AddMyTableMigration.cs
│ ├── NotificationHandlers/
│ └── MyPackage.Core.csproj
├── MyPackage.StaticAssets/
│ ├── App_Plugins/
│ │ └── MyPackage/
│ │ ├── my-property-editor.js
│ │ ├── my-dashboard.js
│ │ └── package.manifest
│ └── MyPackage.StaticAssets.csproj
└── MyPackage.sln
Composer — Entry Point
// Composers/MyPackageComposer.cs
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
[assembly: ComposeAfter(typeof(ICoreComposer))]
public class MyPackageComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// register services
builder.Services.AddSingleton<IMyService, MyService>();
// register event handlers
builder.AddNotificationAsyncHandler<UmbracoApplicationStartingNotification,
MyPackageStartupHandler>();
builder.AddNotificationHandler<ContentPublishedNotification,
ContentPublishedHandler>();
// register migrations
builder.AddNotificationAsyncHandler<UmbracoApplicationStartingNotification,
RunPackageMigrationsHandler>();
// register property type
builder.PropertyEditors()
.Add<MyCustomPropertyEditor>();
}
}
Database Migration
// Migrations/AddMyTableMigration.cs
using Umbraco.Cms.Infrastructure.Migrations;
public class AddMyTableMigration : MigrationBase
{
public AddMyTableMigration(IMigrationContext context) : base(context) { }
protected override void Migrate()
{
if (!TableExists("MyPackageData"))
{
Create.Table<MyPackageDataDto>().Do();
}
else
{
// idempotent column addition
if (!ColumnExists("MyPackageData", "ExtraField"))
{
Alter.Table("MyPackageData")
.AddColumn("ExtraField")
.AsString(512)
.Nullable()
.Do();
}
}
}
}
[TableName("MyPackageData")]
[PrimaryKey("Id", AutoIncrement = true)]
public class MyPackageDataDto
{
[Column("Id")]
public int Id { get; set; }
[Column("ContentId")]
public int ContentId { get; set; }
[Column("Data")]
[NullSetting(NullSetting = NullSettings.Null)]
public string? Data { get; set; }
[Column("CreatedAt")]
public DateTime CreatedAt { get; set; }
}
// Run migrations on startup
public class RunPackageMigrationsHandler
: INotificationAsyncHandler<UmbracoApplicationStartingNotification>
{
private readonly IMigrationPlanExecutor _migrationPlanExecutor;
private readonly ICoreScopeProvider _scopeProvider;
private readonly IKeyValueService _keyValueService;
private readonly IRuntimeState _runtimeState;
// ... constructor
public async Task HandleAsync(
UmbracoApplicationStartingNotification notification,
CancellationToken ct)
{
if (_runtimeState.Level < RuntimeLevel.Run) return;
var plan = new MigrationPlan("MyPackage")
.From(string.Empty)
.To<AddMyTableMigration>("v1.0.0");
using var scope = _scopeProvider.CreateCoreScope();
var upgrader = new Upgrader(plan);
upgrader.Execute(_migrationPlanExecutor, _scopeProvider, _keyValueService);
scope.Complete();
await Task.CompletedTask;
}
}
NuGet Package
<!-- MyPackage.Core.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>MyCompany.UmbracoMyPackage</PackageId>
<Version>1.0.0</Version>
<Authors>MyCompany</Authors>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Description>Custom package for Umbraco 13+</Description>
<PackageTags>umbraco;cms;plugin</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Umbraco.Cms.Core" Version="13.*" />
</ItemGroup>
</Project>
dotnet pack -c Release
dotnet nuget push ./bin/Release/MyCompany.UmbracoMyPackage.1.0.0.nupkg \
--source https://api.nuget.org/v3/index.json \
--api-key $NUGET_API_KEY
Development Timelines
Simple package (service + Composer + migration): 3–5 days. Package with custom property type, dashboard and API controller: 1–2 weeks. Complex package with custom panel section, external API and caching: 3–4 weeks.







