Flowcourier.Umbraco.Foundation
17.5.8
dotnet add package Flowcourier.Umbraco.Foundation --version 17.5.8
NuGet\Install-Package Flowcourier.Umbraco.Foundation -Version 17.5.8
<PackageReference Include="Flowcourier.Umbraco.Foundation" Version="17.5.8" />
<PackageVersion Include="Flowcourier.Umbraco.Foundation" Version="17.5.8" />
<PackageReference Include="Flowcourier.Umbraco.Foundation" />
paket add Flowcourier.Umbraco.Foundation --version 17.5.8
#r "nuget: Flowcourier.Umbraco.Foundation, 17.5.8"
#:package Flowcourier.Umbraco.Foundation@17.5.8
#addin nuget:?package=Flowcourier.Umbraco.Foundation&version=17.5.8
#tool nuget:?package=Flowcourier.Umbraco.Foundation&version=17.5.8
Flowcourier.Umbraco.Foundation
Shared base package for Flowcourier Umbraco extensions. Adds a Flowcourier group to the backoffice Settings section and gives every installed Flowcourier package a consistent settings page there — registered from C# with one line, no frontend needed in the consuming package.
How it works
A package registers its settings page in its composer:
builder.AddFlowcourierSettingsPage<AeoOptions>(
"aeo", "AEO", icon: "icon-rocket", configSectionName: AeoOptions.SectionName);
That's it. Foundation's backoffice entry point asks the server which pages are
registered and builds the Settings-section group at runtime: one menu item per
package, each opening a generic page at
/umbraco/section/settings/flowcourier/{packageId} that renders the package's
effective settings (the values bound from appsettings), grouped and typed
(booleans, strings, numbers, string lists, nested option groups).
- No empty group — if no package registers a page, the Flowcourier group doesn't appear at all.
- Backend-only consumers — the registering package needs no wwwroot, no
umbraco-package.json, no Razor SDK; the descriptor lives in DI. - Read-only (for now) — the page is an effective-settings viewer. The wire
contract already carries
editable/sourceper field, so database-backed editable settings can be added later without breaking consumers (planned precedence: values set in appsettings always win and stay locked).
API
GET /umbraco/management/api/v1/fc-foundation/settings-pages — registered pages
(drives the menu). GET …/settings-pages/{packageId} — the full field payload.
Both require backoffice authentication with Settings-section access.
Document "Analytics" tab
Foundation owns one Analytics tab on every document and fills it with sub-tabs ("panels") contributed by whichever Flowcourier packages are installed, so an editor finds everything about the open page in one place:
| Panel | From |
|---|---|
| Traffic / Page speed / Search Console | Flowcourier.Umbraco.Analytics.Core |
| SEO | Flowcourier.Umbraco.AI.Agents.Seo |
| AI Visibility | Flowcourier.Umbraco.AEO |
A package contributes one by registering an ordinary extension manifest:
{
type: "fcPageAnalyticsPanel",
alias: "Fc.PageAnalyticsPanel.Seo", // unique
name: "Flowcourier SEO Page Panel",
weight: 70, // higher first, left to right
element: () => import("./seo-page-panel.element.js"),
meta: { label: "SEO", pathname: "seo" },
conditions: [{ alias: "Flowcourier.Condition.SeoPagePanel" }],
}
- The tab sets
contentKey,cultureandisNewon the panel element, and re-sets them when the editor switches page or variant. A panel may equally consume the document workspace context itself. conditionsare evaluated as they are anywhere else, so a panel that needs a connected service or a configuration switch gates on it and simply isn't there otherwise.meta.fallback: truemarks a panel that only makes sense when nothing else does (Analytics' "how to connect a provider" guidance): it is shown only while no ordinary panel is available.- A lone panel is still shown as a named tab, so the editor can see what they are looking at; with none the tab itself does not appear. The chosen sub-tab is remembered for the browser session.
- On an unsaved document the tab says so instead of showing panels — every panel reports on a page the server can resolve.
The sub-tabs are text-only and render in umb-body-layout's header slot —
the same strip the document-type editor's tabs sit in, white and full-width
above the padded body. A panel's own controls go on the right of that strip by
exposing panelActions:
get panelActions() {
// A Lit template, a DOM node, or an array of either — or null for none.
return html`<fc-date-range compact .value=${this._range}
@change=${(e) => this._onRangeChange(e)}></fc-date-range>`;
}
The tab reads the property when it renders; dispatch
fc-panel-actions-change on the panel when the answer changes (a range
picked, a status arriving). Actions render in the tab's shadow root, so they
must carry their own styles — UUI components and Foundation's elements do, and
this in a Lit event binding would be the tab, so bind handlers with an arrow.
A panel can also put a dot on its own sub-tab, for "there is something behind this tab you have not looked at". That one is declared on the manifest, because the dot has to show while another sub-tab is open and the tab only creates the element of the panel being looked at:
badge: (scope) => import("./aeo-page-badge.js").then((m) => m.pageBadge(scope)),
// scope is { contentKey, culture }; resolve to { color, title } or null.
color: "positive" paints the dot in the positive colour, anything else is
muted, and title becomes the sub-tab's tooltip. It is asked once per page
and variant, so keep it cheap and share whatever it fetches with the panel
itself.
Charts
Foundation ships a shared charting element for Flowcourier backoffice surfaces, backed by a bundled subset of Apache ECharts (line/bar/pie — Apache-2.0, see the packed THIRD-PARTY-NOTICES.md). Consumers import one module and pass a standard ECharts option:
import "/App_Plugins/fc-foundation/fc-chart.element.js";
<fc-chart style="--fc-chart-height: 240px"
aria-label="Hits per day"
.option=${{ xAxis: {…}, yAxis: {…}, series: [{ type: "bar", data: […] }] }}>
</fc-chart>
The element handles the lifecycle end to end: the chart library loads lazily on
first use (backoffice boot is never taxed), colors/text follow the backoffice
theme (light + dark) via the UUI custom properties, resizing is automatic, and
the instance is disposed when the view unmounts. Advanced escape hatches: the
chart getter (raw ECharts instance) and the ready promise.
Maps
A chart can draw on a world map: set map="world" and use ECharts' geo
coordinate system (geo: { map: "world" } with a scatter or map series).
The element fetches and registers the outline from
/App_Plugins/fc-foundation/vendor/maps/world.json the first time a page
needs it. See VENDORED.md for adding other outlines.
Findings
The shared document tab opens with a strip of findings: what an editor could change on this page, with the evidence and the sub-tab that holds the detail. Every Flowcourier package contributes its own through one contract:
public sealed class MyFindingSource : IFcPageFindingSource
{
public string SourceId => "my-package";
public string DisplayName => "My package";
public Task<IReadOnlyList<FcPageFinding>> GetAsync(FcPageFindingRequest request, CancellationToken ct) => …;
}
// in the composer:
builder.Services.AddTransient<IFcPageFindingSource, MyFindingSource>();
A finding is FcPageFinding(Id, SourceId, Severity, Title, Evidence, Action, Panel?, PropertyAlias?). Foundation runs all sources concurrently for the
document (GET fc-foundation/page-findings?contentKey&culture), each with a
timeout, sorts by severity and shows a status line for a source that failed
— one slow source never blanks the strip. Sources judge a fixed window, the
last Flowcourier:Findings:WindowDays (28) full days against the same
length before, so findings stay stable between visits; each source caches
its own result. Nothing runs on nodes page detection calls data.
Shipped sources: Analytics (traffic drop with the channel that fell, no visits, landing-page bounce and mid-funnel exit against the site median, orphaned page, site searches after the page, server time; not-indexed from the last Search Console inspection), the SEO Agent (queries just off the first page missing from title and headings) and AEO (AI bots read the page, no visitor ever arrived from an assistant).
AI-agent insights
Two contracts in Insights/ let a package read what AI crawlers and assistants
did, without depending on the AEO package that records it:
| Contract | Scope | Consumed by |
|---|---|---|
IFcAeoPageInsights |
One page over a trailing window | The SEO Agent's readiness report, the Copilot's crawler tool |
IFcAeoSiteInsights |
The whole site over a window of UTC calendar days | The Analytics section's Overview dashboard |
Both live here because Foundation is the only package both AEO and its
consumers reference. AEO implements and registers them; consumers resolve them
optionally, so GetService<IFcAeoSiteInsights>() returning null simply
means AEO is not installed and the surface shows less:
if (_services.GetService(typeof(IFcAeoSiteInsights)) is IFcAeoSiteInsights agents)
{
var insight = await agents.GetAsync(new FcAeoSiteInsightRequest(from, to), ct);
}
Two rules the site contract states and implementations must honour: its window
is closed-open over UTC calendar days (so its totals will not match a
trailing days=N window for the same nominal length), and its intent split
excludes spoofed fetches and sums to the headline — which has to be
guaranteed by construction, because AEO's stored intent is not spoof-disjoint.
Page detection
The document Analytics tab only makes sense on web pages, and Umbraco has no
flag for that: the published cache routes settings and data nodes exactly like
pages, and templates prove nothing on a headless site. Foundation decides per
document and culture (GET fc-foundation/page-scope), strongest signal first:
- Configuration under
Flowcourier:Pages—IncludeDocumentTypes(when set, everything else is data),ExcludeDocumentTypes,ExcludeRoutePrefixes— and Umbraco's ownDeliveryApi:DisallowedContentTypeAliases. - Not published, or no public URL for the culture → unknown (the tab stays).
- A template assigned → page.
- Evidence from installed packages (
IFcPageEvidenceSource; Analytics answers "this URL has pageviews") → page. - The front end itself: HEAD/GET on the public URL — 200 with HTML is a page,
404 is data. The public site is
Flowcourier:Pages:SiteUrl, else a package's knowledge (IFcPublicSiteUrlSource; Analytics gives the tracked URL), else Umbraco's own URL — unless the Delivery API is enabled, in which case the front end is assumed to live elsewhere and nothing is probed. - Otherwise unknown: surfaces stay visible with their own "no data" notices.
Verdicts are cached (24 h; 1 h for unknown) and shown under Settings →
Flowcourier → Page detection. On a headless site, list the page types in
IncludeDocumentTypes or set SiteUrl to the front end; both beat every
heuristic.
{ "Flowcourier": { "Pages": { "ExcludeDocumentTypes": ["siteSettings", "dataFolder"], "SiteUrl": "https://www.example.com" } } }
Asset caching
Foundation serves every /App_Plugins/fc-* file with Cache-Control: no-cache: browsers keep the files but revalidate them on each load (a 304
for unchanged ones), so a package upgrade or a file edit shows up at once.
Umbraco only cache-busts the files a manifest names, and the modules those
import would otherwise stay stale under heuristic caching. Applies to all
Flowcourier packages since they all reference Foundation.
Brand icons
<fc-brand-icon> shows the logo of a referrer, search engine, social network
or AI assistant from a curated set vendored from the thesvg package (see
VENDORED.md for the list and how to extend it).
import "/App_Plugins/fc-foundation/fc-brand-icon.element.js";
<fc-brand-icon name="Google"></fc-brand-icon>
<fc-brand-icon name="news.ycombinator.com" url="https://news.ycombinator.com/"></fc-brand-icon>
<fc-brand-icon name="GitHub" hover-scope="uui-table-row"></fc-brand-icon>
<fc-brand-icon name="SomeUnknownBot" fallback="icon-chip"></fc-brand-icon>
name is a brand name, slug or domain; url is tried first by host. Unknown
brands render nothing, unless fallback names an Umbraco icon to stand in for
them — which keeps a column of logos aligned where some rows have no brand
(AEO marks unrecognised AI crawlers that way). At rest the icon is single-colour in the surrounding
text colour; it becomes the coloured brand mark while hovered — the element
itself, or the nearest ancestor matching hover-scope. color forces the
coloured mark; --fc-brand-icon-size sets the size (16px). The FcBrandIcons
export offers the same lookup (resolve, url) for code that needs an image
URL, such as chart labels.
Date-range picker
A shared <fc-date-range> element for every date-ranged Flowcourier view: a
trigger button showing the selected range, and a popover with presets on the
left and two month calendars (From / To, with month and year selects) on the
right, plus Clear / Cancel / Apply — Umbraco UI Library styling throughout.
import "/App_Plugins/fc-foundation/fc-date-range.element.js";
<fc-date-range
.value=${{ from: "2026-08-20", to: "2026-09-18", preset: "last30" }}
@change=${(e) => load(e.detail)}></fc-date-range>
change fires with { from, to, preset } — ISO dates (yyyy-MM-dd, local
calendar days, no time zones) and the preset id or "custom". Clicking a
preset applies immediately; picking days needs Apply. Options: .presets
(your own [{ id, label, range() }]; the built-in list is exported as
DEFAULT_PRESETS from fc-date-range.dates.js), min / max ISO bounds
(max defaults to today), week-start (1 Monday default, 0 Sunday),
clearable (Clear + Apply emits an empty range), compact, disabled. The
date helpers (monthGrid, formatRange, presets…) are a separate pure
module for reuse and testing.
Licensing (for paid Flowcourier features)
Foundation validates the license keys that unlock paid Flowcourier
capabilities (e.g. Translation Pro) — fully offline, no license server,
no phone-home. A key is a signed FC1.… token installed in configuration:
{ "Flowcourier": { "Licenses": { "TranslationPro": "FC1.eyJ..." } } }
(or the equivalent environment variable). Keys are bound to your domains (local development hosts always work) and to the Umbraco LTS cycle they were purchased for; trial keys simply carry an expiry. Whatever the license state, your site keeps working and your data stays readable and exportable — an invalid or missing key only means the paid features behave like the free package.
A package registers its product in its composer and gates its features
through IFcLicenses:
builder.AddFlowcourierLicense("translation-pro", "Translation Pro");
// in a gated service:
if (!_licenses.Get("translation-pro").CheckFeature().IsLicensed) { /* degrade */ }
Dashboards surface the state with the shared banner element — it renders nothing while the product is licensed:
<fc-license-banner product-id="translation-pro"></fc-license-banner>
Requirements
Umbraco 17 (net10.0).
License
Free to use, including commercially — not open source. See the LICENSE.md packed with this package. Paid Pro features and consulting are available from Flowcourier ApS.
© 2026 Flowcourier ApS. All rights reserved.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Microsoft.OpenApi (>= 2.7.5)
- Umbraco.Cms.Api.Management (>= 17.5.0)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Flowcourier.Umbraco.Foundation:
| Package | Downloads |
|---|---|
|
Flowcourier.Umbraco.AEO
Answer Engine Optimization for Umbraco. Exposes your published content to LLMs and AI answer engines by dynamically serving an /llms.txt index, an /llms-full.txt full-content dump, and a Markdown rendering of any page when the URL ends in ".md" — all generated from the live content cache, no editor work required. Also auto-injects schema.org JSON-LD (Organization, WebSite, Article, breadcrumbs, FAQ) into every page and ships an "AI Crawlers" backoffice dashboard that tracks visits from GPTBot, ClaudeBot, PerplexityBot and 150+ other AI crawlers (privacy-safe: analytics store no IPs by default — truncated/hashed storage is opt-in). Classifies crawler intent (training, AI search, user-triggered, scanner), records AI referrals — humans clicking through from claude.ai, chatgpt.com, perplexity.ai — and correlates them into "questions answered" conversions on an AI Referrals dashboard. Verifies claimed crawlers against vendor-published IP ranges and reverse DNS, blocks spoofers (vulnerability scanners wearing AI user agents) with an escalating in-memory-to-durable block list, and ships a Blocking dashboard to block or allow any crawler permanently. Zero-config install via an Umbraco pipeline filter; fully configurable through appsettings. |
|
|
Flowcourier.Umbraco.AI.Agents.Translation
Translation Agent for the Umbraco AI Copilot: translates the open page into a target culture (creating the variant) and offers a send-for-approval flow. Ships the tool, its backoffice renderer, and seeds a named "Translation Agent" into the Copilot. Built on Flowcourier.Umbraco.AI.Core. |
|
|
Flowcourier.Umbraco.AI.Agents.Seo
SEO Agent for the Umbraco AI Copilot: reads the open page, identifies its SEO/metadata fields and suggests optimised values, staged for review. Ships the tool, its backoffice renderer, and seeds a named "SEO Agent" into the Copilot. Built on Flowcourier.Umbraco.AI.Core. |
|
|
Flowcourier.Umbraco.Analytics.Core
Shared base for Flowcourier's analytics packages: the "Analytics" section in the Umbraco backoffice (Realtime, Visitors, Behaviour, Acquisition, Technology dashboards), the "Analytics" tab on every document, and the optional Google PageSpeed Insights and Google Search Console reports. Traffic data comes from a provider package — install Flowcourier.Umbraco.Matomo for Matomo or Flowcourier.Umbraco.GoogleAnalytics for Google Analytics 4. Configured entirely from appsettings; the only database table is the index-status cache, created just for sites that turn Search Console index tracking on. |
|
|
Flowcourier.Umbraco.Analytics.AI
Makes the Flowcourier Analytics data answerable in the Umbraco AI Copilot: tools for a page's traffic, its flow, search queries, index status, AI crawler activity and findings, plus an "Explain and suggest" action that turns a page's findings into three prioritised edits. Every tool returns aggregates only — never individual visitors, visitor ids or IP addresses. Needs Flowcourier.Umbraco.Analytics.Core, a provider package and the Umbraco AI Copilot. Also AI Visibility (Analytics Pro): your prompt set sampled through your own Umbraco AI chat profiles with web search, and what the answers cite, beside AEO's real AI-crawler fetches. |
GitHub repositories
This package is not used by any popular GitHub repositories.