FluentCMS — A Blazor CMS Built on Its Own API
FluentCMS is an open-source content management system for .NET, built on ASP.NET Core and Blazor. It works as a traditional page-building CMS and as a headless CMS, and both run on the same API.
GitHub · fluentcms.com · NuGet packages · Discord
At a glance
- My role: creator and lead maintainer, and the project’s largest contributor
- Started: October 2023
- Adoption: 560+ GitHub stars, 107 forks, and about 30,000 downloads across 38 NuGet packages
- Community: 1,456 commits from 16 contributors
- Runs on: .NET 9, ASP.NET Core and Blazor, with a choice of six databases
- License: MIT
- Status: pre-1.0; latest release v0.0.5
as of September 2026
Why I built it
I wanted a CMS that felt native to modern .NET. That meant Blazor from the start rather than bolted on later, and an API-first core so the same content could power a website, an app, or both. It also meant no lock-in to one database: a team should choose SQL Server, PostgreSQL or MongoDB for its own reasons, not because the CMS demands it.
How it’s built
The admin console and your own apps call the same REST API, with the same token model.
The admin console is just another API client. FluentCMS runs as one ASP.NET Core application that serves the public site, the admin console and a REST API. The Blazor front end never calls the business layer directly. Every read and write goes through typed HTTP clients generated from the API’s own OpenAPI spec. So anything an editor can do in the admin console, a script or an app can do through the same documented API.
Everything is a plugin, including the admin. A page is a layout with plugins placed in its sections. A plugin is an ordinary Razor component plus one entry in a manifest; FluentCMS loads it at runtime and gives it storage for its settings and content. The admin console is built the same way: 11 locked pages under /admin, each hosting one plugin. Building the admin on the public plugin model kept that model honest.
Layouts are HTML, with Blazor components dropped in. Layouts are plain HTML plus Scriban templates, so a designer can write one without touching C#. A tag like <PluginsSection Name="Main" fluentcms /> marks where a Blazor component goes. At render time the layout is split into HTML and component segments, and each component gets the right render mode: plain server-rendered HTML for visitors, interactive Blazor Server for editors in the site builder.
The database is a setting, not a rewrite. Services depend only on repository interfaces. One line in appsettings.json picks the implementation: Entity Framework Core for SQLite, SQL Server, MySQL or PostgreSQL, or a document store with LiteDB or MongoDB. The default is LiteDB, an embedded single-file database, so a fresh install needs no database server at all. A caching layer wraps the repositories without the services knowing it’s there.
Infrastructure sits behind small interfaces. Caching, file storage, email, API-token signing, messaging and template rendering each have their own small interface, with the default implementation registered in one place. Moving uploads from local disk to cloud storage means writing one new provider, not refactoring the app.
How it compares
FluentCMS is the only CMS in this group that pairs Blazor components with plain-HTML layouts, and the only one that runs on a document database. The closest peer is Oqtane, which is also built on Blazor and also drives its UI through its own REST API.
| FluentCMS | Oqtane | Umbraco | Orchard Core | Strapi | |
|---|---|---|---|---|---|
| Platform | .NET + Blazor | .NET + Blazor | .NET (Razor/MVC) | .NET (MVC + Liquid) | Node.js, headless only |
| Blazor components on the site | Built in | Built in | No | A guide, not built in | — |
| Layouts editable without a rebuild | Yes: HTML + Scriban | No: themes are Razor components | Razor views, editable in the backoffice except in Production mode | Yes: Liquid | — |
| Relational databases | SQLite, SQL Server, MySQL, PostgreSQL | SQLite, SQL Server, MySQL, PostgreSQL | SQLite, SQL Server | SQLite, SQL Server, MySQL, PostgreSQL | SQLite, PostgreSQL, MySQL, MariaDB |
| Document databases | LiteDB, MongoDB | None | None | None (YesSql stores documents in SQL) | None (MongoDB dropped in v4) |
| One API for the admin and for headless apps | Yes | Mostly | No: Management API and Delivery API | No: server-rendered admin, separate REST/GraphQL | No: Admin API and Content API |
| Multi-site | Yes | Yes, multi-tenant | Yes | Yes, multi-tenant | One instance per site |
| License | MIT, no paid tier | MIT | MIT, with paid cloud and add-ons | BSD-3-Clause | MIT core, paid Enterprise edition |
| Maturity | Pre-1.0, started 2023 | 1.0 in 2020 | Open source since 2005 | 1.0 in 2021 | Since 2015 |
Competitor facts checked against each project’s official docs and repository on September 28, 2026.
Where FluentCMS stands out:
- Blazor components inside designer-friendly layouts. Oqtane is Blazor-native, but its themes are Razor components that have to be compiled. Orchard Core’s Liquid templates can be edited live, but Blazor isn’t built in. FluentCMS combines the two. Layouts are HTML plus Scriban, edited in the admin console and live without a rebuild. Any Blazor component drops into a layout with a single tag.
- The only one here that runs on a document database. MongoDB and LiteDB sit alongside four SQL databases, behind the same repository interfaces. A team that already runs MongoDB doesn’t need to add SQL Server just for its CMS. LiteDB makes a first install a single file, with no database to provision.
- One API for everything. Umbraco splits its backoffice API from its headless Content Delivery API, and Strapi splits its Admin API from its Content API. In FluentCMS, the admin console, your apps and your scripts all call the same 96 endpoints with the same token and policy model. Anything the admin can do is scriptable, and there’s one API to learn, document and monitor.
- Small enough to own. FluentCMS is about 27,000 lines of hand-written C# and Razor across 39 focused projects. It’s published as 38 NuGet packages under MIT, with no paid tier or enterprise edition. A team can read the whole thing, fork it, or reference only the packages it needs.
Where the others are ahead: FluentCMS is the youngest project here and still pre-1.0. Umbraco and Orchard Core document load-balanced, multi-server deployments, while FluentCMS runs as a single instance today. Orchard Core and Strapi offer GraphQL; FluentCMS is REST only. All four alternatives have larger communities and extension ecosystems. If you need a proven, horizontally scaled CMS today, they’re the safer choice. FluentCMS is for teams that want Blazor end to end, a document database, or a codebase small enough to own outright.
What you get out of the box
- Multi-site hosting from one install, with each site resolved from its domain
- Hierarchical pages with per-page layouts, SEO settings,
sitemap.xmlandrobots.txt - A drag-and-drop site builder with a responsive live preview
- Headless content types with eight field types, served through the API
- Reusable HTML blocks, rich-text editing, a file manager and a contact-form plugin
- Users, roles, and site- and page-level permissions
- API tokens scoped by policy, with the policy list generated from the API itself, so new endpoints show up automatically
- A first-run setup wizard that seeds a new site from a template: Default, Blank or Portfolio
- A 67-component Blazor UI library, styled with Bootstrap 5
- 96 API endpoints across 19 controllers, documented in Swagger
Performance at scale
In 2024 and 2025 I used FluentCMS to explore how far Blazor Server can be pushed. Two experiments stand out:
- Runtime compilation. Components are compiled at runtime with Roslyn into a collectible assembly load context, and proxy components let a running app swap a component for a new version live.
- Render-tree caching. Caching at the renderer level, with Redis pub/sub invalidation, cut a page render from 45 ms to 3.8 ms. In testing, that raised the number of users a single 8-core server could handle from about 250 to about 1,120 and lowered CPU use from 78% to 31%, at an 82.4% cache hit rate.
Both were experiments run on FluentCMS. Neither is part of the current release.
Under the hood: for developers
How a page renders. Every URL that isn’t a file lands on one catch-all Razor component. It asks the API what lives at that URL, renders the layout with Scriban, and splits the result into HTML and component segments. Then it mounts each plugin by type name. Visitors get static server-rendered HTML; editors get the same page as interactive Blazor Server.
Writing a plugin. A plugin is one or more Razor components plus a manifest entry. The component named as the default view renders on the page; one named Edit shows up in the site builder’s menu. FluentCMS loads the DLL into its own load context, caches the type, and wraps every plugin in an error boundary, so one failing plugin can’t take down the page.
Here’s the built-in TextHTML plugin’s manifest entry, and its view component, trimmed:
{
"Name": "TextHTML Plugin",
"Category": "Content",
"Assembly": "FluentCMS.Web.Plugins.TextHTML.dll",
"Types": [
{ "Name": "View", "Type": "TextHTMLViewPlugin", "IsDefault": true },
{ "Name": "Edit", "Type": "TextHTMLEditPlugin" }
]
}
@inherits BasePlugin
@if (Item != null)
{
<div class="f-html-content">@((MarkupString)Item.Content)</div>
}
@code {
private TextHTMLContent? Item { get; set; }
protected override async Task OnInitializedAsync()
{
var response = await ApiClient.PluginContent.GetAllAsync(nameof(TextHTMLContent), Plugin.Id);
Item = response.Data?.ToContentList<TextHTMLContent>().FirstOrDefault();
}
}
Swapping the database. Services only ever see repository interfaces. At startup, the Database setting picks one implementation family, and a caching decorator wraps it without the services knowing.
Moving to PostgreSQL is a configuration change:
{
"Database": "PostgreSQL",
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=fluentcms;Username=fluentcms;Password=..."
}
}
The rest of the infrastructure follows the same pattern: one small interface, one default implementation, registered in one place.
| Interface | Default implementation |
|---|---|
ICacheProvider | In-memory cache |
IFileStorageProvider | Local disk |
IEmailProvider | SMTP |
IApiTokenProvider | Signed JWT |
IMessagePublisher | In-process MediatR |
ITemplateRenderingProvider | Scriban |
Using it headless. Create an API token in the admin console, give it the Content Management → Read policy, and query any content type by its slug:
curl "https://your-site.example/api/Content/blog/GetAll?siteId=<site-id>" \
-H "X-API-AUTH: <key>:<secret>"
Every response uses the same envelope: isSuccess, status, errors and data, plus a trace id and timing.
My role
I started FluentCMS in October 2023 and laid down its layered architecture: API, services, repository contracts and entities. In the first few months I wrote the MongoDB storage layer, the ASP.NET Core Identity store, the dynamic layout-rendering engine and the template-driven setup process.
In 2024 I built the pieces that make it extensible and portable: runtime plugin loading, the API-token and policy system, the repository caching layer, and the Entity Framework Core layer behind all four SQL databases.
Over the project’s life I’ve made about 630 of its 1,456 commits, working with a small core team of regular contributors and a wider group from the community.
Where it’s headed
FluentCMS is pre-1.0. I recently reviewed the whole codebase end to end, and the path to 1.0 has four parts:
- Production hardening: a security pass across every service, plus production-ready defaults for secrets, configuration and error handling
- A safety net: automated tests and pull-request CI, including one contract test suite run against all six databases
- Cloud-ready scale-out: distributed caching, blob storage, a container image, health checks and OpenTelemetry
- A faster render path: letting the Blazor host call services in-process, while keeping HTTP for headless clients
Stack
.NET 9 · ASP.NET Core · Blazor (static SSR + Interactive Server) · Entity Framework Core · ASP.NET Core Identity · JWT · MediatR · Scriban · AutoMapper · Scrutor · NSwag · Swagger · LiteDB · MongoDB · SQLite · SQL Server · MySQL · PostgreSQL · Bootstrap 5 · GitHub Actions · NuGet
Comparison sources
- Oqtane — oqtane.org, render modes, theme structure, client ServiceBase
- Umbraco — requirements, Management API, runtime modes, load balancing, pricing, history
- Orchard Core — database providers, YesSql, Blazor guide, 1.0 release
- Strapi — databases, v4 MongoDB removal, Content API, admin tokens, license