BaryoDev.Umbraco.ReadAloud 0.1.0

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

Read Aloud for Umbraco

Add "listen to this article" to an Umbraco site, using Microsoft Edge's neural voices. The audio is generated and cached on your own server, and nothing about a listener is ever stored.

For Umbraco 16, 17 and 18, on .NET 9 and .NET 10.

dotnet add package BaryoDev.Umbraco.ReadAloud

Nothing else to run: the package registers itself through an Umbraco composer, and every setting has a working default. The design and the plan it was built from are in the repository.


Read this first: the endpoint is not supported by Microsoft

This uses the same free endpoint Microsoft Edge uses for its own read-aloud feature. It is not a supported Microsoft API. There is no contract, no SLA and no commitment that it will look the same next year. Microsoft could change or close it without notice and would owe nobody an explanation.

It is unsupported, but it is neither obscure nor new. Measured 15 August 2026:

edge-tts on PyPI 14,645,228 downloads/month
node-edge-tts on npm 8,603,847 downloads/month
rany2/edge-tts on GitHub 11,727 stars
First released May 2021, still actively maintained

Roughly 23 million downloads a month, and five years of this path being used at scale without Microsoft closing it. That is why building on it is reasonable. The absence of a contract is why there is a fallback rather than none.

There is no paid provider in this version. Azure Speech is not implemented: there is no Azure engine in this package, and no credentials are read anywhere. If you need a supported service with a contract behind it, register your own IReadAloudEngine and the package will use it.

If the endpoint fails at runtime, the browser client falls back to window.speechSynthesis, which is built into every modern browser and costs nothing. Quality is worse and word highlighting is less precise, and the client says so rather than pretending otherwise.

Two tiers: free and good, and free and always available underneath it.

What it does

  • A <read-aloud> element that reads a configured property of the current page
  • Word-by-word highlighting driven by real timings from the engine, not estimates
  • Audio cached on disk, so the same article is never synthesized twice
  • Lazy generation, so publishing is instant and nothing is synthesized until somebody asks

Usage

Load the client on any page you want a button on, then add the element:

<script type="module" src="/App_Plugins/BaryoDev.ReadAloud/readaloud.js"></script>

<read-aloud node="@Model.Key" for="#article-body" voice="en-GB-SoniaNeural"></read-aloud>
  • node: the published content key of the page to read. Required
  • for: a selector to the element whose words get highlighted as they are spoken, and whose text is read by the speechSynthesis fallback if the server route fails. Optional; without it the button still plays, just without highlighting
  • voice: optional, honoured only if the site's configuration allows it
  • base: optional path prefix for a site not mounted at the root (an IIS virtual application, UsePathBase, and similar). The client works the prefix out from the URL this script was served from, so it is only needed if you serve the file from somewhere other than App_Plugins

No build step: the file above is a plain ES module, committed as-is in the package.

What it does not do

  • Store anything about a listener. No table, no migration, no IP, no user agent, no identity. The only thing written anywhere is derived audio you can delete at any time
  • Call any third party except the speech service itself. No analytics, no telemetry
  • Require a build step. No npm, no bundler

Configuration

v1 is configuration only. Every value has a working default.

"BaryoDev": {
  "ReadAloud": {
    "Enabled": true,
    "DocumentTypes": [ "article", "blogPost" ],
    "PropertyAlias": "bodyText",
    "DefaultVoice": "en-GB-SoniaNeural",
    "AllowedVoices": [ "en-GB-SoniaNeural", "en-US-JennyNeural", "fil-PH-BlessicaNeural" ],
    "MaxChars": 8000,
    "CachePath": "App_Data/BaryoDev/ReadAloud",
    "RateLimitPerMinute": 20,
    "MaxConcurrentSynthesis": 4,
    "Provider": "Edge"
  }
}

Provider

"Edge" is the only value this version implements, and it is the default, so there is nothing to set. Any other value stops the site at startup with a message naming the setting. That is deliberate: a provider name that looked configured and quietly left every request on the unofficial endpoint would be worse than a failed boot. To synthesize somewhere else, register your own IReadAloudEngine rather than naming it here.

RateLimitPerMinute and what "per IP" means

The limit is a fixed one-minute window per source IP as the server sees it, which is the address of whatever connected to it. On a site behind Cloudflare, nginx, a load balancer or any other reverse proxy, that is the edge's address and not the reader's, so every visitor on the site shares a single bucket and ordinary readers start getting 429 Too Many Requests.

If your site sits behind a proxy, configure ASP.NET's forwarded headers middleware (UseForwardedHeaders, with ForwardedHeaders.XForwardedFor) and restrict it to your proxy's addresses. Without it this setting does not do what its name suggests, and raising the number only delays the problem.

The address is used as an in-memory partition key for the length of one window. It is never written to disk, never logged, and never part of a cache key.

MaxConcurrentSynthesis

A ceiling on how many articles are being synthesized at once across the whole site, which is a different quantity from the rate limit: the rate limit counts requests arriving, and one synthesis of a long article outlives the request that asked for it, deliberately, so the cache is filled for whoever asks next. Past the ceiling a request is refused with a 503 and the reader gets the browser's own voice, rather than waiting on a queue with no end. Four is deliberately modest, because a burst of connections is what gets an unofficial endpoint closed.

The browser half comes from @baryodev/read-aloud, which is published and framework-agnostic. This package is the Umbraco-side sibling: it reads the property server-side, which is the thing a framework-agnostic package cannot do.

Contributing

Genuinely welcome, including small changes. See CONTRIBUTING.md, and look for good first issue.

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.1.0 120 8/17/2026