Issues

PostgreSQL Support for Umbraco

For years, PostgreSQL has been a quiet powerhouse in the open‑source database world — robust, fast, and enterprise‑ready. It offers performance advantages over SQL Server in many scenarios, especially when indexes aren’t perfectly optimized, and it consumes significantly less RAM. Compared to SQLite, PostgreSQL is far better suited for production workloads and large‑scale installations.

Yet despite these strengths, Umbraco has historically supported only SQL Server and SQLite. That gap eventually led me down a long, fascinating path: building a full PostgreSQL database provider for Umbraco.

This article tells that story — why I started, what I discovered inside Umbraco’s persistence layer, the architectural changes required, and what it means for the future.

Why I Started This Project

My first exposure to PostgreSQL came years ago when a colleague benchmarked it against SQL Server and found it consistently faster. That result stuck with me. I kept wondering why Umbraco didn’t support PostgreSQL, especially when other CMS platforms like TYPO3 had embraced it.

Fast‑forward to last year. During a difficult burnout phase, I noticed how rare Umbraco projects had become in Germany and how dependent Europe remains on U.S. software vendors. With some time to reflect, I dug into Umbraco’s source code — and discovered something surprising:

  • NPoco already supports PostgreSQL.
  • Umbraco has an abstraction layer (ISqlSyntaxProvider).

That was enough to spark a deep dive. I wanted to see how far I could get.

Very quickly, I found the core issue: Umbraco didn’t consistently use the SQL abstraction layer. Many raw SQL statements bypassed NPoco’s PostgreSQL‑aware mechanisms, causing problems — especially around case sensitivity.

Fortunately, most required changes were isolated to the Umbraco.Infrastructure persistence layer. I submitted several PRs, all reviewed by Andy Butland, and the work gradually took shape.

My PostgreSQL provider will ship as a package when Umbraco 17.3.0 releases on April 2nd, 2026, and I plan to maintain it until Umbraco eventually replaces NPoco with EF Core.

Key Changes Required for PostgreSQL Support

1. Case Sensitivity Fixes

PostgreSQL is case‑sensitive in ways SQL Server is not. Raw SQL like:

var sql = "DELETE FROM umbracoContentVersionCleanupPolicy WHERE contentTypeId = @id";

had to be rewritten using NPoco’s quoting helpers:

var sql = $"DELETE FROM {QuoteTableName(ContentVersionCleanupPolicyDto.TableName)}
WHERE {QuoteColumnName("contentTypeId")} = @id";

This ensures identifiers are correctly quoted for the active database provider.

2. Replacing Raw SQL With Strongly Typed NPoco Methods

NPoco provides generic, type‑safe methods that automatically generate database‑specific SQL. For example:

.Select("sortOrder")

became:

.Select(c => c.SortOrder)

A more complete transformation:

Before:

var result = _scopeAccessor.AmbientScope.Database
    .ExecuteScalar("SELECT COUNT(*) FROM umbracoUser");

After:

var db = _scopeAccessor.AmbientScope.Database;

var sql = db.SqlContext
    .Sql()
    .SelectCount()
    .From<UserDto>();

var result = db.ExecuteScalar<int>(sql);

This ensures SQL generation is consistent across providers.

3. Fixing ExecuteScalar&lt;Guid?&gt; Behavior

NPoco’s PostgreSQL implementation requires nullable GUIDs to pass through the full mapping pipeline. That means replacing ExecuteScalar<Guid> with FirstOrDefault<Guid>.

Correct approach:

var sql = sqlContext.Sql()
    .Select<NodeDto>(c => c.UniqueId)
    .From<NodeDto>()
    .Where<NodeDto>(n => n.NodeId == id);

return database?.FirstOrDefault<Guid?>(sql);

This avoids provider‑specific failures.

4. New SQL Extensions: SelectMax

To replace raw MAX() queries, I added two new extension methods:

SelectMax<TDto>(Expression<Func<TDto, object?>> field)
SelectMax<TDto>(Expression<Func<TDto, object?>> field, int coalesceValue)

This allowed transformations like:

Before:

SELECT coalesce(max(sortOrder),0)
FROM umbracoNode
WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType

After:

sql = Sql()
    .SelectMax<NodeDto>(c => c.SortOrder, 0)
    .From<NodeDto>()
    .Where<NodeDto>(x =>
        x.ParentId == entity.ParentId &&
        x.NodeObjectType == NodeObjectTypeId);

var maxSortOrder = Database.ExecuteScalar<int>(sql);

Cleaner, safer, and provider‑agnostic.

5. Extended DTOs

The DTOs are the perfect place to control table and column names. Actually they to did this already, but for the consistent use in hard code strings like raw SQL statements it is quite useful to expose these as constants of the DTO. For example you could use ContentVersionCleanupPolicyDto.TableName instead of "umbracoContentVersionCleanupPolicy".

This is especially useful when you mix it with typed queries like Sql().Select&lt;ContentVersionCleanupPolicyDto&gt;().

Compatibility With Commercial Packages

So far, I’ve tested only Umbraco Forms, and several issues surfaced. Because the package is closed‑source, I couldn’t fix them directly. I’ve implemented workarounds for most features, but a few roadblocks remain.

If enough interest grows, perhaps Umbraco HQ will support deeper integration. Until then, contributions are welcome.

Closing Thoughts

This project began as a curiosity—an experiment during a difficult period. It grew into something meaningful: a full PostgreSQL provider for Umbraco, built on open‑source principles and a desire for more European technological independence.

The work is far from finished, but it’s a solid foundation. And with Umbraco 17.3.0, PostgreSQL support becomes a real, installable option for the community.

If you’re interested in contributing, testing, or exploring the provider, I’d love to collaborate.


You'll find the package on NuGet: Our.Umbraco.PostgreSql.

Dirk Seefeld

Dirk has been a computer enthusiast since he was young. Nevertheless, he studied graphic design and started his career in desktop publishing. After a few years he worked for a multimedia agency, where he programmed and animated interactive games for children.

Since 1997 he has focused on web development and has created many marketing websites. In 2009 he discovered Umbraco CMS and it was love at first sight. And after his first Codegarden in 2010, he was sure that Umbraco, its community and philosophy would influence the rest of his life.

Dirk lives with his wife and small dog in Hamburg, Northern Germany. He relaxes by listening to fantasy and science fiction stories, watching related videos or playing such computer games.

comments powered by Disqus