Lait.Umbraco.GoToContent 1.0.5

dotnet add package Lait.Umbraco.GoToContent --version 1.0.5
                    
NuGet\Install-Package Lait.Umbraco.GoToContent -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Lait.Umbraco.GoToContent" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lait.Umbraco.GoToContent" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="Lait.Umbraco.GoToContent" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Lait.Umbraco.GoToContent --version 1.0.5
                    
#r "nuget: Lait.Umbraco.GoToContent, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Lait.Umbraco.GoToContent@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Lait.Umbraco.GoToContent&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=Lait.Umbraco.GoToContent&version=1.0.5
                    
Install as a Cake Tool

Lait.Umbraco.GoToContent

One click from the live page to the content editor.

Go To Content drops a floating "Edit in Umbraco" pill in the top-right corner of your public-facing pages. It is shown only to editors who are already logged in to the Umbraco backoffice — regular visitors never see it. Clicking it opens the content editor for the exact node you're looking at, in a new tab.

No more copy-pasting node IDs or hunting through the content tree. See something to fix on the live site → click → you're editing it.

The "Edit in Umbraco" pill on a public page

Install

dotnet add package Lait.Umbraco.GoToContent

That's it. The package self-registers through an IComposer — zero configuration. Build, run, and browse your site while signed in to /umbraco.

How it works

  • A lightweight middleware runs late in the Umbraco pipeline and inspects each HTML response.
  • If — and only if — the current request belongs to an authenticated backoffice user, it injects a small inline <style> plus the pill's own markup just before </body>.
  • The pill is a plain <a> pointing at the backoffice deep-link for the current published node (/umbraco/section/content/workspace/document/edit/{key}/{variant}). No JavaScript is involved, so a strict script-src Content-Security-Policy can't suppress it.
  • Anonymous visitors get a byte-for-byte unmodified response — nothing is injected, nothing leaks.

The injected markup is self-contained (no external requests), respects prefers-color-scheme, hides itself in print, and is accessible (focusable, aria-label).

Compatibility

Package Target frameworks Umbraco .NET
Lait.Umbraco.GoToContent 1.0.x net9.0, net10.0 15 – 17 9.0 / 10.0

The net9.0 assembly serves Umbraco 15 and 16; the net10.0 assembly serves Umbraco 17. The upper bound is open — newer Umbraco majors on .NET 9/10 are expected to work and will be added to this matrix as they're verified.

Need Umbraco 14? Umbraco 14 runs on .NET 8, which can't load a net9.0 assembly. Open an issue if you need a net8.0 build.

Configuration

None required. The pill text is "Edit in Umbraco" and it is positioned top-right by default.

Troubleshooting

"I'm logged in to the backoffice but the pill disappears after a while"

This is the most commonly reported problem, and it is a configuration issue rather than a bug.

Since Umbraco 14 the backoffice is a SPA that keeps itself signed in using OpenIddict access and refresh tokens. Your public pages never see those tokens — they only see the UMB_UCONTEXT cookie. Umbraco configures that cookie with SlidingExpiration = false and an expiry of Umbraco:CMS:Global:TimeOut (20 minutes by default), and renews it only when Umbraco:CMS:Security:KeepUserLoggedIn is true.

So roughly twenty minutes after signing in, the cookie quietly expires while your backoffice tab carries on working perfectly. You are still logged in as far as /umbraco is concerned, but front-end requests now look anonymous — and the pill vanishes. Signing out and back in brings it back for another twenty minutes, which is what makes this look intermittent and user-specific.

To fix it, let Umbraco renew the cookie on each request:

{
  "Umbraco": {
    "CMS": {
      "Security": {
        "KeepUserLoggedIn": true
      }
    }
  }
}

Raising Umbraco:CMS:Global:TimeOut widens the window but does not remove the cliff, because the cookie still never slides.

Does the pill work in preview mode?

Yes. Browsing your site in Umbraco preview shows the pill as normal, and it deep-links correctly — a node's Key is the same for its draft and published versions.

Earlier versions suppressed the pill whenever the UMB_PREVIEW cookie was present. Because that cookie is scoped to / and lasts the whole browser session, an editor who entered preview and never explicitly exited it lost the pill on every ordinary page until they closed the browser. The pill is now hidden only inside nested documents (an <iframe>, detected via Sec-Fetch-Dest), which is the case it was actually meant to avoid.

The pill never appears at all

Turn on debug logging for the package — every decision not to inject is logged with its reason:

{
  "Logging": {
    "LogLevel": {
      "Lait.GoToContent": "Debug"
    }
  }
}

Then load a page while signed in and check the log. Common causes:

Log says Cause
No UMB_UCONTEXT cookie The cookie isn't reaching this host. Check Umbraco:CMS:Security:AuthCookieDomain, and note it is marked Secure when Umbraco:CMS:Global:UseHttps is true, so it is never sent over plain HTTP.
cookie present but the backoffice ticket was rejected The expired-cookie case above.
found no </body> Your layout doesn't emit a literal </body>. An HTML minifier with removeOptionalTags, or a stray space as in </body >, defeats the match.
response was already encoded UseResponseCompression() is running inside app.UseUmbraco(). Move it so it wraps app.UseUmbraco() instead.
No published node The page isn't routed by Umbraco (a custom controller or Razor Page), so there is no content node to link to.
is a nested document The page is being rendered inside an <iframe> (for example the backoffice preview frame), where a fixed-position pill would sit over the embedding chrome rather than the page.

Nothing logged at all means the middleware isn't registered: the host must call .AddComposers() and use app.UseUmbraco().WithMiddleware(...) (the standard template does both).

License

MIT © Lait

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 137 8/18/2026
1.0.4 105 8/17/2026
1.0.3 144 7/7/2026
1.0.2 111 6/19/2026
1.0.1 112 6/19/2026
1.0.0 115 6/19/2026

Fixes for the pill failing to appear. Declares the Umbraco dependency again (required for Umbraco Marketplace listing). The pill is now static HTML instead of being built by an inline script, so a strict script-src CSP no longer suppresses it. Preview mode no longer hides the pill site-wide for the rest of the browser session; only nested documents (iframes) are skipped. Reserved paths are matched on segment boundaries, so pages such as /installation are no longer excluded. The node is resolved lazily, so virtual and custom-routed pages get a pill. Cache headers are applied before the response starts, fixing an InvalidOperationException. Every decision not to inject is now logged with its reason under the Lait.GoToContent category.