This is the framework I use to reason about an existing Umbraco platform. It is not a list of generic best practices and it is not a substitute for Umbraco Health Checks, security testing or performance profiling. The goal is to connect technical evidence to decisions: what is healthy, what is risky, what blocks future change, and what should deliberately be left alone.

What an Umbraco Technical Audit Is Actually For

Imagine inheriting a five-year-old Umbraco platform. Editors publish every day. Deployments usually work. There is no major incident this week. The site may even be fast on the homepage.

Is the platform healthy?

You cannot answer that from uptime, a Lighthouse score or a green build. The next major upgrade may be blocked by an abandoned package. A content model may encode assumptions that make a new market expensive. A request may look fast only because a cache is hiding unnecessary database work. An integration may work until the provider slows down. A deployment may be repeatable only because one developer remembers three manual steps.

That is why I do not treat an audit as a checklist exercise. I treat it as an investigation of change cost, operational risk and technical ownership.

Health Checks Are Evidence, Not the Audit

Umbraco includes Health Checks for configuration, data integrity, live-environment settings, permissions and security-related configuration, and custom checks can be added. They are useful evidence. A green Health Check dashboard, however, cannot tell you whether your content model is coupled to templates, whether an integration has a safe retry model, or whether custom code will block the next major upgrade.

The audit question is not “does this project follow every best practice?” It is “where is this platform fragile, expensive to change, difficult to operate or likely to block a future business decision?”

1. Start With Context Before Looking at Code

The easiest audit mistake is opening the solution and immediately judging what you see. Architecture only makes sense relative to the system it supports.

Before reviewing implementation, I want to understand the platform: who edits it, which markets and domains it serves, expected traffic, critical user journeys, deployment frequency, recovery expectations, integrations, current pain points, planned features and the next likely Umbraco upgrade.

This changes how findings are interpreted. An in-memory application cache can be perfectly reasonable on one instance and incorrect when the same code is deployed to several instances without a coherent invalidation strategy. A synchronous integration may be acceptable for a low-volume internal operation but dangerous in checkout. A custom property editor that has been stable for years may be low priority until the next backoffice migration changes its compatibility story.

Question

Why I ask it

What must never fail silently?

Identifies business-critical boundaries and observability needs.

What changes are expected in the next 12 to 24 months?

Turns technical debt into change-specific risk.

What requires a particular developer to deploy or diagnose?

Reveals undocumented operational ownership.

Which integrations are outside your control?

Identifies latency, contract and failure boundaries.

Which Umbraco version is next?

Makes upgrade blockers measurable now, not during the upgrade.

2. Establish the Platform Baseline

I create a factual system inventory before writing findings. At minimum this includes the Umbraco and .NET versions, hosting model, database, media storage, search/indexes, packages, custom extensions, environments, domains, CDN, external services, CI/CD path and monitoring.

This sounds basic, but it often exposes uncertainty immediately. If nobody can explain which application owns an integration credential, whether media is local or external, or how schema changes reach production, that uncertainty is itself useful evidence.

Technical system context for an Umbraco platform showing delivery, CMS, persistence, search, media, integrations and deployment

A platform baseline makes ownership and external boundaries visible before individual code findings are discussed.

The diagram does not need to model every class. It needs to answer a more valuable question: what does this platform depend on to serve and manage content?

3. Audit Architecture and Dependency Boundaries

I do not score an Umbraco solution by how closely its folder structure resembles Clean Architecture. I look at dependency direction and the cost of a real change.

Take one important operation and follow it. Where does HTTP end? Where does application behavior live? Which code knows about Umbraco APIs? Which code knows about a payment, CRM or search provider? Can an integration failure be handled in one place, or does provider-specific behavior leak through controllers, views and notification handlers?

A typical smell is not “there are too few interfaces”. It is that a volatile boundary has no owner. The opposite smell also exists: every class has an interface, but most interfaces simply mirror one implementation and protect no meaningful decision.

public interface ICustomerService
{
    Task<Customer> GetAsync(Guid id);
}

public sealed class CustomerService(ICustomerRepository repository): ICustomerService
{
    public Task<Customer> GetAsync(Guid id) => repository.GetAsync(id);
}

This may be harmless, but it is not automatically architecture. During an audit I ask what this seam allows the system to change, test or protect. If the answer is “nothing yet”, I do not create a finding just because the layer is thin. I look for the places where boundaries actually matter.

4. Treat the Content Model as Architecture

In a CMS, architecture is not only C# projects and dependency injection. The content model is a long-lived public interface between developers, editors, templates, imports, APIs and future migrations.

A document type with 60 properties is not automatically wrong. The risk appears when unrelated responsibilities accumulate and other parts of the system start depending on incidental structure.

Content model coupling diagram showing a page type connected to templates, tree location, integrations, imports and editor workflow

The expensive part of a content model is often not its size but the number of assumptions coupled to it.

For example, suppose a ProductLandingPage has several compositions, templates traverse ancestors to find configuration, an import depends on a fixed parent node, and an integration reads property aliases directly. Renaming or splitting the type is no longer a schema edit. It can require data migration, code changes, template changes, import changes, regression testing and editor retraining.

That is why I inspect document types, element types, compositions, Block Grid structures, allowed children, mandatory tree positions, property semantics and reusable settings together. I also ask whether the model reflects the domain or merely the sequence in which features were added.

A useful finding describes the coupling. “This document type has too many properties” is weak. “Changing this type affects four rendering paths, an import and a tree-location assumption” is actionable.

5. Audit How the Application Reads and Writes Umbraco Data

One of the most useful Umbraco-specific checks is whether runtime rendering uses the right data access path.

Umbraco distinguishes management services from read-only helpers and published-content APIs. The official guidance is explicit that IContentService is for modifying content and should not be used in a view or template to fetch data for display, because that path hits the database rather than the published content cache.

A typical anti-pattern

public sealed class NavigationService(IContentService contentService)
{
    public IEnumerable<IContent> GetNavigation()
    {
        return contentService
            .GetRootContent()
            .SelectMany(x => contentService.GetPagedChildren(
                x.Id, 0, 100, out _));
    }
}

If this is executed during page rendering, I would investigate it immediately. The exact replacement depends on where the code runs, but the runtime read path should normally use published content. In code guaranteed to execute with an Umbraco context, that may be IPublishedContentQuery. In services that can run without an existing context, IUmbracoContextFactory can establish one safely.

public sealed class NavigationService(IPublishedContentQuery publishedContentQuery)
{
    public IEnumerable<IPublishedContent> GetNavigation()
    {
        return publishedContentQuery
            .ContentAtRoot()
            .SelectMany(x => x.Children);
    }
}

The important finding is not “replace one API with another”. It is that a rendering path is doing management/database work when Umbraco already has a published-content path designed for display.

6. Follow the Expensive Request Paths

I do not start a performance audit by adding caching. I choose a small number of important requests and follow where their time goes.

Critical Umbraco request path showing rendering, published content, Examine, SQL, external API and media dependencies

Performance becomes easier to reason about when expensive I/O is attached to a specific request path.

A page can be slow because of repeated content traversal, database access, an N+1 query, an external API waterfall, image processing, search, serialization or infrastructure. Those problems have different fixes.

Umbraco's caching guidance makes a related point: optimize first, then cache deliberately, and account for invalidation in load-balanced environments. Caching a bad query path can improve the benchmark while preserving the architectural problem.

I therefore want evidence such as request timings, traces, database/query information, dependency latency, cache hit behavior and cold-start behavior. Examine indexes also deserve attention on search-heavy sites. A cold restart can require index rebuilds, and the amount of content affects how expensive that startup path becomes.

7. Audit Integrations by Their Failure Modes

An integration is not healthy merely because a successful request returns the expected JSON.

For every important dependency I want to know the contract, authentication model, timeout, cancellation behavior, retry policy, idempotency assumptions, error mapping, rate limits, observability and owner.

public async Task<PaymentResult> AuthorizeAsync(PaymentRequest request, CancellationToken cancellationToken)
{
    using var response = await httpClient.PostAsJsonAsync(
        "/payments",
        request,
        cancellationToken);

    if (response.StatusCode == HttpStatusCode.PaymentRequired)
        return PaymentResult.Declined();

    response.EnsureSuccessStatusCode();

    return PaymentResult.Authorized();
}

This snippet may be fine or dangerously incomplete. The audit questions are outside the happy path. What happens after a timeout when the provider may already have processed the payment? Is retry safe? Is there an idempotency key? Can support correlate the local order with the provider transaction? What does the user see while the outcome is unknown?

Integration risk lives in uncertainty. I care less about whether the wrapper class is elegant than whether the system knows what to do when the dependency is slow, unavailable or ambiguous.

8. Audit Security Without Pretending It Is a Penetration Test

A technical Umbraco audit can review security posture, but I keep the scope precise. I inspect configuration, exposed surfaces, backoffice access, permissions, secrets, package/dependency risk, HTTPS, headers, authentication assumptions and operational practices. I do not describe that work as a penetration test unless an actual security-testing methodology is being performed.

Umbraco's built-in Health Checks provide a useful baseline for several configuration and security concerns, including runtime mode, application URL, click-jacking protection, MIME sniffing protection, permissions and HTTPS-related configuration. They should be reviewed, not treated as proof that the whole application is secure.

I also look for secrets committed to configuration files, unnecessary production backoffice exposure, stale users, over-broad permissions and custom endpoints whose authorization model differs from the rest of the platform.

9. Audit Deployment and Operational Recoverability

A green pipeline is not enough. The stronger question is: can another competent engineer reproduce, deploy and recover this platform without relying on tribal knowledge?

I review how code, configuration, schema, content-related artifacts, database migrations and environment-specific settings move between environments. I want to know what is automated, what is intentionally manual and what is accidentally manual.

Production runtime configuration also matters. Umbraco's Production runtime mode changes development-oriented behavior, and the application URL should be configured correctly for production scenarios that generate absolute links such as emails.

A deployment finding should name the hidden dependency

“Deployment is manual” is too broad. A better finding is:

Release succeeds only if an engineer manually:
1. creates an App Service setting,
2. imports a schema artifact,
3. warms a specific endpoint,
4. verifies an Examine index before enabling traffic.

Now the risk is visible. We can decide which steps should be automated, documented, validated or deliberately retained as a controlled manual gate.

10. Audit Upgradeability Before the Upgrade Is Urgent

Upgrade readiness is one of the highest-value parts of an Umbraco audit because it turns future migration cost into evidence while there is still time to choose.

I inventory Umbraco packages, direct dependencies, custom backoffice extensions, APIs used from Umbraco internals, custom database work, forks and integrations. Then I map them against the likely target version.

Major Umbraco upgrades can also move the underlying .NET version, and official upgrade guidance explicitly recommends checking package compatibility. Version-specific upgrade notes matter because framework and dependency changes can affect custom code even when the CMS upgrade path itself is supported.

Dependency

Current state

Target compatibility

Decision

Package A

Maintained

Supports target

Upgrade normally

Package B

Abandoned

No target release

Replace or internalize required behavior

Custom backoffice extension

Business critical

Uses changed extension APIs

Prototype migration before main upgrade

Direct NPoco usage

Custom reporting

Major dependency changed

Compile and integration-test early

The finding is not “the project is old”. The finding is the specific dependency or customization that turns the next supported move into engineering work.

11. Use Production Evidence, Not Only Code Review

Code tells me what the system can do. Production evidence tells me what it actually does.

I want logs, exception patterns, request and dependency timings, failed background work, restart behavior, search/index problems, resource pressure and recurring operational tickets. In a multi-server environment I also want to know whether logs are centralized enough to reconstruct an incident across instances.

This frequently changes priorities. A theoretically ugly component that has been stable and is scheduled for removal may be low priority. A small integration method that accounts for most checkout latency may be high priority.

If production evidence contradicts my first impression from the code, I investigate the evidence. An audit should reduce uncertainty, not defend the reviewer's initial opinion.

12. Audit SEO and Accessibility Where the Platform Controls Them

I do not turn a platform audit into a full marketing SEO audit or a complete accessibility certification. I inspect the parts the CMS and rendering architecture control.

For SEO that includes canonical handling, redirects and status codes, sitemap generation, robots directives, metadata fallbacks, structured data, pagination where relevant, duplicate routes and image handling. For accessibility I inspect semantic rendering, heading behavior, alternative text capabilities, editor-controlled link text, form output, keyboard-sensitive custom components and whether the content model makes accessible authoring practical.

The useful distinction is ownership. A CMS cannot guarantee that every future editor writes excellent alternative text, but the platform can make the field available, explain its purpose, validate where appropriate and avoid rendering patterns that make correct output impossible.

From Technical Evidence to Audit Decisions

The first twelve steps collect context and evidence. Before prioritizing findings, I separate broad observations into the specific failure mechanisms they contain. That decomposition is what makes the next decision defensible.

Field Example: One Finding Can Contain Several Failure Modes

A useful audit finding should not stop at a code smell. In one anonymized production review, a scheduled background process looked like a single implementation problem. Following the execution path showed several independent risks: the job woke far more often than the business action required, loaded an entire growing dataset before filtering it, retrieved credentials through Umbraco content, performed synchronous I/O, and swallowed exceptions. Treating that as one generic “background job issue” would have hidden the decisions that actually mattered.

The important step is decomposition. Unbounded data retrieval affects growth characteristics. Secrets stored as content affect security boundaries and operational ownership. Synchronous external I/O affects execution time and failure propagation. Swallowed exceptions affect observability. Excessive scheduling affects unnecessary background load. Some of those may deserve an immediate fix; others may only justify investigation after production evidence is collected.

This is why I do not assign severity from aesthetics. I want evidence of exposure, probability, impact, change frequency, recovery difficulty and business context. The same implementation can be tolerable in a small internal tool and unacceptable on a high-traffic platform with strict availability requirements.

13. Turn Findings Into Decisions

A 70-page report containing 120 observations is not automatically a useful audit. Findings need evidence and a decision context.

Audit finding flow from signal and evidence through impact and probability to remediation decision

A finding becomes valuable when evidence is connected to impact and a realistic remediation decision.

Finding

Evidence

Impact

Decision

Package blocks next major upgrade

No compatible target release; project depends on removed API

Upgrade cannot complete unchanged

Replace, rewrite required behavior or defer upgrade with an explicit cost

Rendering uses management service

Trace and code show database work on page requests

Unnecessary I/O and poorer scaling behavior

Move runtime reads to published-content path, then remeasure

Tree position is an integration contract

Import resolves parent by fixed hierarchy

Content restructure can break import

Introduce stable identifier/configuration before restructuring

I usually separate severity from priority. A severe problem in a component being retired next month may not deserve immediate investment. A medium technical limitation that blocks a strategic launch in six weeks may be the first item to fix.

14. Not Every Finding Should Be Fixed

This is where audits often become unrealistic. A reviewer sees code they would not write today and turns every difference into remediation work.

I want a stronger standard. Does the issue create material risk? Does it increase the cost of a planned change? Does it make operations unreliable? Is the affected component expected to survive long enough for the investment to pay back?

Suppose a legacy integration has poor internal structure but is being removed in six months. Rewriting it for architectural purity may create more risk than leaving it alone. The right audit outcome can be:

Decision: accept temporarily.
Reason: replacement already funded for Q1.
Control: add missing timeout and alerting only.
Do not refactor the internal design.

That is not ignoring technical debt. It is managing it deliberately.

15. What a Useful Umbraco Audit Should Produce

The deliverable should make the next decisions easier. Depending on scope, I expect some combination of:

  • a verified platform and dependency baseline;

  • a system context or dependency map;

  • evidence-backed findings rather than generic recommendations;

  • risk and business-impact classification;

  • upgrade blockers and package compatibility concerns;

  • quick wins separated from structural changes;

  • unknowns that still require investigation;

  • a prioritized remediation roadmap;

  • explicit findings that are accepted or intentionally deferred.

I also want the report to preserve uncertainty. If a finding depends on traffic data we do not have, I say that. If a suspected issue was not reproduced, it should not quietly become a fact in the executive summary.

Final Perspective

A useful Umbraco audit does not tell you whether a project follows every fashionable pattern. It tells you where the platform is fragile, expensive to change, difficult to operate or likely to block the next business decision, and which of those problems are actually worth fixing.

The best outcome is not a longer backlog. It is a clearer technical model of the platform: what we trust, what we need to change, what we need to measure, and what we can safely leave alone.

Frequently Asked Questions

What is an Umbraco technical audit?

An Umbraco technical audit is a structured review of how an existing platform behaves across architecture, content modelling, data access, performance, integrations, security boundaries, deployment, operations and upgradeability. The useful output is evidence-backed engineering decisions, not a list of code smells.

What should an Umbraco audit include?

The exact scope depends on the platform, but a serious review normally establishes context and a technical baseline, follows important request and operational paths, examines architecture and content coupling, reviews integrations and deployment, uses production evidence where available, and turns findings into prioritized decisions.

When is an Umbraco audit worth doing?

It is most valuable when uncertainty is expensive: before a major upgrade or rebuild, during recurring performance or reliability problems, before taking ownership of a mature platform, or when technical debt is making delivery increasingly difficult to predict.

Are Umbraco Health Checks enough for a technical audit?

No. Health Checks are useful evidence for configuration, security and environment conditions, but they do not explain the full architecture, runtime behavior, content coupling, custom integrations, deployment model or the business impact of a finding.

Does an Umbraco technical audit include performance, SEO and security?

It can review the parts of performance, technical SEO and security that are controlled by the platform and relevant to the agreed scope. That is different from claiming a full penetration test, marketing SEO engagement or exhaustive accessibility audit.

How should Umbraco audit findings be prioritized?

Prioritize findings by evidence, impact, context and the decision they enable. A practical outcome is Fix, Investigate, Accept or Monitor. Severity alone is not enough because the same technical condition can have very different consequences in different platforms.

Should every technical debt finding be fixed?

No. Some debt is stable, understood and cheaper to accept than to remove. A good audit distinguishes conditions that create material cost or risk from imperfections that do not justify intervention.

References