BaryoDev.Umbraco.Pwa 0.5.0

dotnet add package BaryoDev.Umbraco.Pwa --version 0.5.0
                    
NuGet\Install-Package BaryoDev.Umbraco.Pwa -Version 0.5.0
                    
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="BaryoDev.Umbraco.Pwa" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BaryoDev.Umbraco.Pwa" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="BaryoDev.Umbraco.Pwa" />
                    
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 BaryoDev.Umbraco.Pwa --version 0.5.0
                    
#r "nuget: BaryoDev.Umbraco.Pwa, 0.5.0"
                    
#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 BaryoDev.Umbraco.Pwa@0.5.0
                    
#: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=BaryoDev.Umbraco.Pwa&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=BaryoDev.Umbraco.Pwa&version=0.5.0
                    
Install as a Cake Tool

PWA for Umbraco

Turn an Umbraco site into an installable, offline-capable app, and see who installed it from the backoffice you are already signed into.

Free, MIT, and self-hosted. The install data lands in your own database and never leaves your server, so there is no third-party dashboard to log into and no processor to declare.

Install

dotnet add package BaryoDev.Umbraco.Pwa

That is the whole installation. On the next start the package creates its table, registers its endpoints, and adds a PWA dashboard to the Settings section.

Then add two lines to your site layout:

<link rel="manifest" href="/manifest.webmanifest" />
<script src="/baryodev-pwa.js" defer></script>

The script registers the service worker and reports install status. There is no build step, no npm dependency and no JavaScript to write.

Configure

Everything has a working default. A site that adds nothing to appsettings.json still gets a manifest, a service worker and install tracking.

{
  "BaryoDev": {
    "Pwa": {
      "Manifest": {
        "Name": "Contoso",
        "ShortName": "Contoso",
        "ThemeColor": "#1b1721",
        "Icons": [
          { "Src": "/media/icon-192.png", "Sizes": "192x192", "Type": "image/png" },
          { "Src": "/media/icon-512.png", "Sizes": "512x512", "Type": "image/png" },
          { "Src": "/media/icon-maskable.png", "Sizes": "512x512", "Type": "image/png", "Purpose": "maskable" }
        ]
      },
      "ServiceWorker": {
        "CachePrefix": "contoso",
        "ApiPrefix": "/api/",
        "SkipPaths": ["/umbraco/"]
      }
    }
  }
}

Chrome will not offer to install a site without a 192px and a 512px icon, so those two entries are the difference between the package working and appearing not to.

Setting Default Notes
TrackInstalls true Off leaves the app behaviour and collects nothing
TrackInstalledOnly false On stores nothing until someone actually installs
RetentionDays 0 Zero keeps rows forever
ServeAssets true Off if the site already ships its own sw.js
ServiceWorker.Version assembly version Change per deploy to purge stale cached assets

It tells you why it is not working

Browsers enforce installability silently. Miss a 512px icon, or point at one that 404s, and Chrome simply never offers to install your site. Nothing errors and nothing logs.

The dashboard runs a preflight and names the failing condition:

  • served over HTTPS (a service worker will not register otherwise, localhost excepted)
  • manifest has a name
  • display mode is app-like rather than browser
  • a 192px and a 512px icon are configured and actually reachable
  • a maskable icon, advisory: without one Android crops your icon into a white circle

This check exists because it caught a real failure while this package's own demo was being deployed.

What it records

One row per browser, keyed by an id the browser generates for itself:

platform, display mode, whether it has ever run installed, first and last seen, and a launch count.

No IP address, no user agent string, no visitor identity. The table answers "how many people installed this, on what" without becoming a record of who visited.

Endpoints

Method Route Access
POST /umbraco/pwa/api/report anonymous, by necessity
GET /umbraco/management/api/v1/baryodev/pwa/summary Settings section
GET /umbraco/management/api/v1/baryodev/pwa/installs Settings section
GET /umbraco/management/api/v1/baryodev/pwa/readiness Settings section
GET /manifest.webmanifest, /sw.js, /baryodev-pwa.js anonymous

A well-formed report always returns 202 with no body, whether it was stored or rate limited. It is best-effort telemetry, so a client should never retry over it, and an answer that varied with the report's content would let an anonymous caller probe which device ids exist. Malformed requests are rejected on shape: 400, 415 or 413.

Using it with a decoupled front end

The report contract is identical to @baryodev/pwa-kit, so a Next.js or Nuxt front end talking to Umbraco headless can use the npm package and post to the same endpoint:

import { reportPwaStatus } from "@baryodev/pwa-kit";

reportPwaStatus((report) =>
  fetch("https://cms.example.com/umbraco/pwa/api/report", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(report),
  }),
);

The dashboard does not care which client sent the report.

Verified, not assumed

57 tests run against a real Umbraco booting on SQLite, covering the migration, the endpoints, the generated assets and the dashboard registration. CI runs the whole suite against every supported major, because a version range in a csproj is a claim and this is the evidence for it:

Umbraco Runtime Support Tests
16 .NET 9 standard-term 57 / 57
17 .NET 10 current LTS, into late 2027 57 / 57
18 .NET 10 latest 57 / 57

Beyond that, the package was deployed to a live Umbraco 18 behind nginx and driven with a real browser, which confirmed end to end:

Service worker registered at root scope, https://.../sw.js
Caching shell populated; the backoffice stayed out of it after a live /umbraco/ fetch
Install prompt beforeinstallprompt fired and the banner rendered with a working Install button
Tracking the visit reached the database, and a second visit incremented the launch count rather than adding a row

Requirements

Umbraco 16, 17 or 18. The package multi-targets .NET 9 and .NET 10 and NuGet picks the right one.

Umbraco 15 is not supported. It is the last major with the synchronous MigrationBase, which 16 replaced with AsyncMigrationBase. That break lands between 15 and 16 while the runtime break lands between 16 and 17, so the two do not align and one assembly cannot span both. 15 is also standard-term support and past its one-year window.

Versions

From 1.0 this package follows semantic versioning across five public surfaces, not just the assembly API: the configuration keys, the report contract, the database table and the generated asset URLs are equally public, and breaking any of them breaks a site rather than a build. VERSIONING.md says what each covers, which test gates it, and which Umbraco versions are supported.

Contributing

Contributions are welcome, and the issue list is written to be picked up by someone who has never seen this code. Every issue says what is wrong, why it matters and where to start, rather than leaving you to work that out first.

Good places to begin:

  • Good first issues, which are genuinely small rather than nominally small.
  • #18 needs no C# at all. The icons and logo here are developer placeholders and it shows, so a designer would move the needle further than another feature would.
  • #15 and #21 are the roadmap, and they explain the reasoning rather than just listing features. Worth reading before picking anything up, and disagreeing with either of them on the issue is a useful contribution in itself.

Comment /take on an issue to assign yourself, or just say you are interested and we will sort it out. Running the whole thing locally is one command, and CONTRIBUTING.md has the rest.

There is a live demo at dev-playground.baryo.dev if you would rather see it work before checking anything out.

Licence

MIT.

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
0.5.0 105 9/2/2026
0.4.0 103 8/27/2026
0.3.0 110 8/18/2026
0.2.0 116 8/17/2026
0.1.0 108 8/13/2026

A security release. The readiness check can no longer be used to probe the network the server sits on: the guard now runs in the connection callback, so it covers every redirect hop. Pages rendered for a signed-in visitor are no longer stored by the service worker, where Cache Storage would have served them to the next person using the device. The anonymous report endpoint is rate limited and its body is capped, and deviceId is checked against a character set rather than only truncated. The worker's skip list is now matched the way ASP.NET routes, so /Umbraco/ and /umbraco/ are excluded alongside /umbraco/ rather than caching the backoffice. Every identity on a request is checked rather than only the first, so a backoffice preview of a draft is no longer stored under the published URL. System.Security.Cryptography.Xml is pinned per target framework, which raises the floor for your site too. Full changelog: https://github.com/BaryoDev/umbraco-pwa/blob/main/CHANGELOG.md