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
<PackageReference Include="BaryoDev.Umbraco.Pwa" Version="0.5.0" />
<PackageVersion Include="BaryoDev.Umbraco.Pwa" Version="0.5.0" />
<PackageReference Include="BaryoDev.Umbraco.Pwa" />
paket add BaryoDev.Umbraco.Pwa --version 0.5.0
#r "nuget: BaryoDev.Umbraco.Pwa, 0.5.0"
#:package BaryoDev.Umbraco.Pwa@0.5.0
#addin nuget:?package=BaryoDev.Umbraco.Pwa&version=0.5.0
#tool nuget:?package=BaryoDev.Umbraco.Pwa&version=0.5.0
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 | Versions 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. |
-
net10.0
- System.Security.Cryptography.Xml (>= 10.0.11)
- Umbraco.Cms.Api.Management (>= 17.0.0 && < 19.0.0)
-
net9.0
- System.Security.Cryptography.Xml (>= 9.0.19)
- Umbraco.Cms.Api.Management (>= 16.0.0 && < 17.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
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