Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

The analytics kit — Plausible or Google Analytics 4, with consent

app.Analytics = new AnalyticsSetup { Provider = AnalyticsProvider.Plausible, Domain = "shop.example.com" }; · Analytics(events: [], cart: [], currency: "SEK", onSent: Heard)

`Osysharp.WebAnalytics` reports an app's page views and e-commerce events to Plausible or Google Analytics 4. The app picks the provider in its config and grants that provider's hosts on its `use` line; `Analytics()` in the layout does the rest. GA4 loads nothing until the visitor accepts the consent bar; Plausible needs no consent.

preview1 example compiled by CIuikitanalyticsshop

Source on GitHub: Osysharp.WebAnalytics — github.com/osysharp/webanalytics ↗

Summary#

Osysharp.WebAnalytics counts visits and sales with Plausible or Google Analytics 4. You choose the provider in the app's config, grant the hosts it talks to on the kit's use line, and put Analytics() in your layout once. Every page view is then reported, along with any e-commerce events the layout hands it.

The events are GA4's recommended e-commerce names: view_item, add_to_cart, begin_checkout and purchase. A shop built on Osysharp.Shop does not hand it any: it writes ShopAnalytics() in its frame instead, which derives all four from the shop's own rows.

// app.osy
use Osysharp.WebAnalytics@0 { egress browser "plausible.io"; }

Signature#

[AppSetting("Analytics")] class AnalyticsSetup
    // Provider · Domain · Host (Plausible) · MeasurementId (GA4)
enum AnalyticsProvider            // None · Plausible · GoogleAnalytics
class AnalyticsEvent              // Name · Key · Currency · Value · TransactionId · Tax · Shipping · Items
class AnalyticsItem               // Id · Name · Brand · Category · Variant · Price · Quantity

component Analytics(AnalyticsEvent[] events, AnalyticsItem[] cart, string currency, Action<string> onSent)
component AnalyticsConsentBar(Action accept, Action decline)
control AnalyticsTag              // the browser half — probe: sent · lastPageView · lastViewItem · lastAddToCart ·
                                  //   lastBeginCheckout · lastPurchase · purchases · loaded · consent · dryRun
AnalyticsConfig AnalyticsConfiguration()   // the setup, as the page needs it

Description#

Choosing a provider#

app.Analytics is read fresh on every page, so changing it and recompiling is all a switch takes.

ProviderSetGrant on use Osysharp.WebAnalytics@0 { … }
PlausibleDomain — the site as registered in Plausibleegress browser "plausible.io";
Plausible, self-hostedDomain, and Host = "https://stats.example.com"egress browser "stats.example.com";
GA4MeasurementId = "G-XXXXXXXXXX"egress browser script "*.googletagmanager.com"; egress browser "*.google-analytics.com"; egress browser "*.analytics.google.com";

With Provider = None, or no domain or id set, nothing is loaded and nothing is sent.

No snippet is pasted into the page#

An app page carries a Content-Security-Policy that admits code and connections from the app's own origin only (see What an app page is allowed to load). The kit does not get around it. Its browser code is its own control, pinned by content hash like any control, and the hosts it reaches are exactly the ones the app grants. The kit's manifest says egress browser consumer;, because which host is the app's choice: Plausible may be self-hosted. The grants appear with every other egress grant in osy egress list and in the Admin app's Config tab. A host nobody granted is refused by the browser.

  • Plausible is spoken to through its Events API from the kit's control. No third-party script runs on your page, so the grant is egress browser (data), not egress browser script.
  • GA4 has no browser client other than Google's tag, so the tag is loaded, from the host you granted as egress browser script. That grant is the largest a package can ask for, which is one more reason it waits for consent.

Plausible sets no cookie and keeps no personal data, so the kit asks nothing and reports from the first page.

GA4 sets cookies, and in the EU it may not track before the visitor agrees. With GA4, Analytics() shows AnalyticsConsentBar along the foot of the page: a Decline and an Accept of equal weight. Until the visitor accepts, nothing of Google's is loaded: no script, no request, no cookie. Events from before consent are dropped, not queued, because an event from before consent is what consent exists to prevent. Accepting counts the page it was given on. The answer is kept in the visitor's browser, so the bar does not ask again. Consent mode is set explicitly: analytics storage as answered, advertising storage always denied, because the visitor was asked about counting visits, not about ads.

Events#

events are the events true of the page being viewed, such as "this page shows product X". Each is reported once per page view, identified by its Key. A purchase is reported once in a browser, however often the page is reloaded: its key is remembered before it is sent. cart is the visitor's bag. Whatever grows in it is an add_to_cart, and a bag that loads empty first is not read as "everything was just added". onSent hears each event's key once it has gone, which is how the shop stops offering a sale it has reported.

Plausible receives the events as goals named View Item, Add to Cart, Begin Checkout and Purchase, and a purchase carries its revenue (Plausible's revenue tracking). Create those goals in Plausible to see them. GA4 receives its own names and parameters (GA4 e-commerce).

A shop: ShopAnalytics()#

Osysharp.Shop depends on this kit. A store puts ShopAnalytics() in its frame, beside ShopVisitSync(), and reports:

  • view_item on a product's page (ShopSetup.ProductPath), for the size a visitor sees first;
  • add_to_cart for whatever grows in the bag, wherever it was added from;
  • begin_checkout on the checkout (ShopSetup.CheckoutPath, /checkout unless set);
  • purchase once per order. It is reported from the order, not from a thank-you page, which can be reloaded or never reached. Placing an order with analytics on leaves a sale for the visit that placed it. The frame reports it once the order is paid, or at once for a way to pay with no payment page (a bank transfer, the counter), and then it is gone.

A domain that cannot be a site#

A Plausible Domain under a reserved name (.example, .test, .invalid, .localhost) cannot be a Plausible site. The control records its events and sends none, and its probe says dryRun. That is what a demo or a test wants: the events are real and assertable, and nothing leaves the machine.

Testing it#

The control publishes a probe, so a UI test asserts what was reported:

Ui.Visit("/p/kiambu-aa");
Assert.Probe("AnalyticsTag", "lastViewItem", "Kiambu AA (250 g)");
Ui.Click("Add to bag");
Assert.Probe("AnalyticsTag", "lastAddToCart", "1 × Kiambu AA (250 g)");

Examples#

Plausible, in a layout#

app AnalyticsExample {
  use Osysharp.Ui;
  use Osysharp.WebAnalytics@0 { egress browser "plausible.io"; }
  model "model/**/*.osy";
}

using Osysharp.WebAnalytics;

app.Analytics = new AnalyticsSetup { Provider = AnalyticsProvider.Plausible, Domain = "kettles.example.com" };

[Layout]
[AllowAnonymous]
component Site() {
  action Heard(string key) { }
  render {
    Stack(gap: 4) {
      Analytics(events: [], cart: [], currency: "SEK", onSent: Heard);
      Outlet();
    }
  }
}

[Route("/")]
[Layout(Site)]
[AllowAnonymous]
component Home() {
  render { Text("Kettles, and nothing else."); }
}

See also#

Related

package.osy

The manifest that makes a git repository a publishable Osy# package. It names the package, says which of the…

What an app page is allowed to load

Every app page is served with a Content-Security-Policy the browser enforces. Scripts, styles, fonts, workers and data…

control — foreign UI controls (charts, grids, maps)

A `control` block declares the contract of a foreign UI widget — a chart, a data grid, a map — that a small JavaScript…

probe — what a control says about itself

A `probe { }` block declares the facts a control publishes about its OWN internal state, so an app's tests can ask for…

Product feeds — Google Merchant Center and Meta

A store built on `Osysharp.Shop` serves its catalogue as product feeds: `/feeds/google.xml` for Google Merchant Center…