Modeto.Intelligence 0.9.7
Modeto Intelligence
Block-level personalization and A/B testing for Umbraco 18, by 8Bitiz.
- Its own Modeto section in the backoffice top menu, with Overview, Analytics, Audiences, Intent, Goals, Experiments, Visitors and Traffic.
- Audiences with a rule builder: visits, visited page or section, campaign / source / medium, device, browser, time of day, weekday, member logged in or registered, events, goals reached. Rules can be negated and combined with all/any; audiences have a priority order, an optional end date and a control group that measures the lift.
- Block-level personalization: show a different Block Grid / Block List block per audience, inside one property.
- Block-level and split-URL A/B tests with live Bayesian results (rate, 95% credible interval, probability to be best), audience targeting, a traffic share, scheduling, a runtime estimate, variant previews, editing, and a finish flow that shows the winner to everyone and can apply it to the page.
- Live analytics: visits, bounce rate, time on page, engaged time, scroll depth, pages, referrers, campaigns, devices/browsers/OS, custom events, goals. Plus an Analytics tab on every content item.
- Decaying intent: personas and journey stages scored from pages, campaigns, sources, referrers, events and goals. Scores halve every half-life instead of expiring, so a visitor's persona follows what they do now.
- CRM identity: member groups and member properties (or your own CRM via
IModetoCrm), andIModetoIdentityto link a visitor to a CRM contact; audiences can use them. - Audience activation (marketing consent only): activated audiences go to the site's own Google Tag Manager / gtag / Meta Pixel tags, and Customer Match lists (hashed e-mails of identified members) sync to Meta Custom Audiences and Google Ads (or download as CSV).
- Goals (macro/micro, value) completed by a page visit, a client-side event or your own code. A/B tests measure a goal.
- Visitor profiles: visit timeline (pages, engagement, events, goals), members identified when they log in, filters, CSV export and a per-visitor data download.
- Page cache per audience set, so personalized pages still render once per audience combination.
- Privacy built in (KVKK/GDPR): nothing is tracked or cookied without consent, data retention clean-up, erase a visitor, no IP addresses stored.
- Traffic rules: block IPs/ranges and user agents, and spot visitors with suspiciously many page views.
Everything runs in-process and stores its data in the site's own database by default, or in a separate database of your choice (see Configuration). No external service.
Requirements
Umbraco 18.2.1 or later 18.x, on .NET 10.
Install
Modeto is installed from its own NuGet feed, https://nuget.hamilton.kudyu.com/v3/index.json, next to nuget.org (Umbraco and MaxMind.GeoIP2 come from there). Add it to the solution's nuget.config with a source mapping, so Modeto.* packages only ever come from that feed:
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="modeto" value="https://nuget.hamilton.kudyu.com/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="modeto"><package pattern="Modeto.*" /></packageSource>
<packageSource key="nuget.org"><package pattern="*" /></packageSource>
</packageSourceMapping>
</configuration>
The feed needs the user name and password you were given. Keep them out of the solution's nuget.config: on your computer add them to your own NuGet settings (dotnet nuget add source https://nuget.hamilton.kudyu.com/v3/index.json --name modeto --username <user> --password <password> --store-password-in-clear-text --configfile ~/.nuget/NuGet/NuGet.Config), on build servers set NuGetPackageSourceCredentials_modeto to Username=<user>;Password=<password>. Then:
dotnet add package Modeto.Intelligence
From a local folder of .nupkg files instead, use the folder's path as the modeto source in that nuget.config. Don't use --source: it restricts the restore to that source, which fails on a machine that doesn't already have Umbraco and MaxMind.GeoIP2 cached.
Start the site. A package migration creates everything Modeto needs (it runs once, and again only for new versions):
- data type Modeto Audience and element type Modeto Personalization (
modetoAudience,modetoVariantGroup); - 15 tables, all named
modeto*:modetoAudience(seeded with three example audiences),modetoPersona,modetoGoal,modetoExperiment,modetoBlockRule(settings),modetoVisitor,modetoSession,modetoPageview,modetoEvent,modetoGoalCompletion,modetoExposure,modetoLookup(tracking),modetoDailyPage,modetoDailyEvent(daily summaries),modetoActivationSync(ad platform sync status). Add-ons add their own (the Forms companion:modetoFormSettings). The tables are checked and completed at every start; - access to the Modeto section for the Administrators group.
To give other users the section, add it to their group in Users > User groups > Sections. Users without it can still pick audiences on blocks, but can't see or change audiences, experiments or visitor data.
No template changes are needed: Block Grid and Block List values personalize themselves when a template reads them.
Set up a block editor (once per site)
Personalization settings live in each block's settings:
- In Settings > Data Types, open your Block Grid or Block List.
- For each block type that should be personalizable:
- if it has no settings model, set Settings model to Modeto Personalization;
- if it already has one, add Modeto Personalization as a composition of that settings element type.
- Optional: add
{modeto:audience}to the block's Label, e.g.{=headline} {modeto:audience}. The label then shows the block's audience and variant group as a tag.
Consent (required by default)
Modeto tracks, personalizes and A/B tests a visitor only with consent. Until then: no cookies, no tracking, default content. Your cookie banner sets the consent with the included helper, then reloads the page:
<script src="/App_Plugins/Modeto.Intelligence/consent.js"></script>
<script>
// after the visitor chooses:
modetoConsent.set(['analytics', 'personalization', 'abtesting']); // or ['all']; modetoConsent.clear() to withdraw
location.reload();
</script>
Categories: analytics, personalization, abtesting, marketing (or all). Personalization, A/B testing and marketing also need analytics (they use the visitor cookie). marketing lets Modeto pass a visitor's activated audiences to your ad tags and include them in Customer Match lists; ask for it separately (KVKK requires explicit consent for sharing with ad platforms). Withdrawing consent removes Modeto's cookies.
With Cookiebot, for example (map the categories as your privacy policy says):
window.addEventListener('CookiebotOnConsentReady', () => {
const c = Cookiebot.consent, parts = [];
if (c.statistics) parts.push('analytics');
if (c.statistics && c.preferences) parts.push('personalization', 'abtesting');
if (c.statistics && c.marketing) parts.push('marketing');
parts.length ? modetoConsent.set(parts) : modetoConsent.clear();
});
To read your consent platform directly instead, register your own IModetoConsent (builder.Services.AddSingleton<IModetoConsent, MyConsent>()).
To track everyone without consent, set Modeto:Consent:RequireConsent to false.
Configuration (appsettings.json, all optional)
"Modeto": {
"Consent": { "RequireConsent": true, "CookieName": "modeto_consent" },
"Retention": { "PageviewDays": 365, "VisitorDays": 365, "SummaryDays": 1095, "BatchSize": 5000 },
"SuspiciousActivity": { "PageviewThreshold": 250 },
"Analytics": { "InjectScript": true },
"TimeZone": "Europe/Istanbul",
"Crm": { "MemberProperties": ["customerTier"] },
"Scale": { "ColumnstoreIndexes": false, "MonthlyPartitions": false, "PartitionMonthsAhead": 3 }
}
Retention: a daily job deletes page views, visits, events, goal completions and A/B exposures older than
PageviewDays, and visitors not seen forVisitorDays(with their data), in batches ofBatchSizerows (one transaction per batch). Daily summaries are kept forSummaryDays(default 1095), so page reports reach further back than the raw page views.0keeps data forever. Status and "Run clean-up now" are on Modeto > Overview.ConnectionStringName (optional): keep all Modeto tables in another database, so tracking growth and report queries stay off the CMS database (recommended for busy sites). Add a connection string and point Modeto at it; Modeto creates and updates its tables there at startup. On SQL Server the database must exist; SQLite creates the file.
"ConnectionStrings": { "modetoData": "Server=...;Database=ModetoData;...", "modetoData_ProviderName": "Microsoft.Data.SqlClient" }, "Modeto": { "ConnectionStringName": "modetoData" }Switching an existing site starts the new database empty. To keep what Modeto already has in Umbraco's database (settings such as audiences, goals, personas, experiments and traffic rules, and all tracking data), use Copy data from Umbraco's database in the Database box on Modeto > Overview once the site runs on the new database. It replaces everything in the new database, keeps ids, and runs in the background; meanwhile pages show default content and new tracking is held, then written on top of the copy. Umbraco's database is only read, so its Modeto tables can be dropped by hand once you're happy. On a load-balanced site, run it with one server up.
If the Modeto database becomes unreachable, the site keeps working: pages render default content (no personalization or A/B tests), tracking events are held and written in order when it's back (in memory up to 50,000; older ones, and everything held at shutdown or a deployment, go to disk in
umbraco/Data/Modeto/spoolon that server, up to 500 MB, and are deleted once written), and background jobs pause. Modeto > Overview shows the database status, and every Modeto page says so in its footer with a Check again button (lists say they can't load, and reload by themselves within 30 seconds of the database coming back). For a database on another server, keep connection attempts short so a network problem can't hold pages up, and let the client retry brief drops, e.g.Connect Timeout=5;ConnectRetryCount=3;ConnectRetryInterval=5(SQL Server; Azure SQL also benefits from these).Analytics:InjectScript: Modeto adds its client script (
/App_Plugins/Modeto.Intelligence/modeto.js) before</body>of tracked HTML pages. Set it tofalseand include the script tag yourself if you'd rather control it (it is never added twice).TimeZone: for "time of day" and "weekday" audience rules (IANA or Windows id). Default: the server's time zone.
Location: country and city for the "Location" audience rule, the Analytics > Location report and country block rules. IP addresses are never stored. Two built-in sources, chosen on Modeto > Settings:
- CDN headers (default):
CF-IPCountry/CF-IPCity(Cloudflare; the city needs the "Add visitor location headers" managed transform),CloudFront-Viewer-Country/CloudFront-Viewer-City(Amazon CloudFront; forward them to the origin), orX-Country-Code/X-Cityfrom your own proxy. Change the lists withCountryHeaders/CityHeaders. On a site that isn't behind such a CDN, setEnabledtofalse(visitors could send the headers themselves). - MaxMind GeoLite2 (optional): create a free MaxMind account, generate a license key, and enter the account ID and key on Modeto > Settings, then switch it on. Modeto downloads the database (City or Country) to
umbraco/Data/Modetoon each server, refreshes it weekly, and looks up the visitor's IP in memory; the CDN headers stay as a fallback. The key is stored encrypted (ASP.NET Core data protection; on load-balanced sites share the data protection keys, as Umbraco requires anyway) and is never shown again. Addumbraco/Data/Modeto/to.gitignore(MaxMind's license doesn't allow redistributing the file). Behind a proxy or load balancer, configure ASP.NET Core forwarded headers so the IP is the visitor's: the simplest is the environment variableASPNETCORE_FORWARDEDHEADERS_ENABLED=true(only when every request comes through a proxy that setsX-Forwarded-For; otherwise visitors could send a fake IP). - Or register your own
IModetoLocationin a composer; it replaces both.
- CDN headers (default):
Activation (optional): credentials for syncing activated audiences to ad platforms. Leave them out and the CSV downloads still work; a platform syncs once all its values are set. Keep them out of source control: use
dotnet user-secrets, environment variables (Modeto__Activation__Meta__AccessToken=...) or a key vault."Modeto": { "Activation": { "SyncHours": 24, "Meta": { "AccessToken": "<system user token, ads_management>", "AdAccountId": "act_1234567890" }, "Google": { "DeveloperToken": "<developer token>", "ClientId": "<OAuth client id>", "ClientSecret": "<OAuth client secret>", "RefreshToken": "<refresh token with the adwords scope>", "CustomerId": "123-456-7890", "LoginCustomerId": "<manager account id, if any>" } } }SyncHours: hours between automatic syncs (0 = only Sync now).ApiVersion(Metav25.0, Googlev25) can be overridden when the platforms retire a version.Scale (SQL Server only; ignored on SQLite): for big sites, applied by a background job a minute after start and then hourly.
ColumnstoreIndexes: nonclustered columnstore indexes on the raw tables (page views, visits, events, A/B exposures, goal completions), so reports over months read much less. Needs SQL Server 2016+ or an Azure SQL tier with columnstore (S3 and up, or vCore). Setting it back tofalsedrops them.MonthlyPartitions: clusters those tables by time and splits them by month (PartitionMonthsAheadempty months are kept ready). The first run rebuilds each table: online on Enterprise/Developer and Azure SQL, otherwise the table is locked while it runs (minutes on a big table), so switch it on at a quiet time. It can't be switched off again (the tables stay partitioned).
Raw events elsewhere: for sites beyond what reports on SQL Server handle (roughly a million page views a month), register an
IModetoEventSinkto copy every stored batch of tracking events to ClickHouse, BigQuery, Azure Data Explorer or a data lake. It runs on the tracking writer's thread, in order, after the batch is saved: queue the work and return quickly.public class ClickHouseSink(MyQueue queue) : IModetoEventSink { public void Write(IReadOnlyList<TrackingEvent> events) => queue.Enqueue(events); // PageviewEvent, CustomEvent, GoalEvent, ... } // in a composer: builder.Services.AddSingleton<IModetoEventSink, ClickHouseSink>();Crm:MemberProperties: member property aliases copied as CRM traits (for
crmaudience rules) when a member is on the site; reloaded every 15 minutes.Erase a visitor (data-subject requests): Modeto > Visitors, open the visitor, Erase visitor.
Use it
Personalize a block: hover a block, choose Personalize this block. You get a copy right after it, in the same variant group, for the next audience. Visitors in that audience see the copy; everyone else sees the default (no audience) block.
Audiences: Modeto > Audiences. Each rule can be is or is not; a visitor must match all rules or any rule. When a visitor is in several audiences with a block in the same variant group, the audience higher in the list wins (move audiences up and down). An end date makes a temporary audience. A control group (0 to 50%) keeps showing the default block to a stable share of the audience; the page then shows how often members and the control group completed a macro goal, and the probability that personalizing converts better.
Intent: Modeto > Intent. A persona (or journey stage: any group name) gets points per signal: a page (and pages below it), campaign, source, medium, referring site, event or goal. Points halve every half-life days. Per group, the visitor's strongest persona at or above its threshold is theirs. Use it in audiences with the Intent rule (the visitor's persona, or a minimum score).
CRM: rules Member group and CRM trait (
tier=gold, ortierfor any value). Traits come fromModeto:Crm:MemberProperties, your ownIModetoCrm(e.g. a call to your CRM by member e-mail; return segment values, not personal data, since they are kept in the visitor cookie), or from code for visitors who aren't members:// e.g. after a form post; links the visitor to your CRM contact and adds traits identity.Identify(HttpContext, crmContactId, new Dictionary<string, string> { ["tier"] = "gold" });Activation: turn on Activate on an audience. For visitors with marketing consent,
modeto.jspasses its alias once per session to Google Tag Manager (dataLayereventmodeto_audience), gtag (eventmodeto_audienceand user propertymodeto_audiences) and the Meta Pixel (fbq('trackCustom', 'ModetoAudience', { audience })); build audiences on those in GA4 / Google Ads and Meta. Other tags can listen to themodeto:audienceswindow event or readmodeto.audiences. With credentials configured, Modeto keeps one list per activated audience on each platform ("Modeto:", created on the first sync) and replaces its members on every sync: Meta with usersreplace, Google Ads with a Customer Match job (consent sent as granted; only visitors with marketing consent are included). The audience shows each platform's last sync, member count or error, and a Sync now button. Meta can't empty a list through the API, so an audience with no members leaves its Meta list as it was. Deleting an activated audience, or turning its activation off, removes its lists on the platforms; anything that couldn't be removed (a platform error, credentials gone) is shown so you can remove it by hand. Meta list / Google list download a Customer Match CSV of SHA-256 hashed e-mails of identified members in the audience (marketing consent only, control group left out), normalized as each platform requires.Goals: Modeto > Goals. A goal is completed by visiting a page (paths, matching the pages below them too, or picked pages), by a client-side event (
CategoryorCategory|Action,*allowed), or by code. Each goal counts once per visit. Macro goals make up the conversion rate.A/B test a block: choose A/B test this block to add variant B (C, ...) to the group, edit it, publish, then create the test in Modeto > Experiments with a goal. Visitors who match an audience block see that block and are not in the test. Options:
- Audience and Traffic: only visitors in an audience, and only a share of them, take part; everyone else sees variant A and isn't counted.
- Start / End: schedule the test; it starts and stops by itself (page caches are cleared at those moments).
- Page: the page with the blocks; the test then has Preview A / B / ... links (
?modeto_preview=<test>:<letter>, not tracked or cached). - Minimum effect (default 20%): the runtime estimate shows roughly how many more days the test needs to detect that lift over A.
- Edit: name, page, audience, traffic, end and minimum effect can change at any time; the goal, start (and split-URL pages) only while the test is scheduled. Changing the traffic share moves some visitors in or out of the test.
- Finish with a winner: the test stops and everyone sees the winning variant (cacheable again). Apply winner to page then removes the other variants of the group from the page and saves a draft (blocks with an audience stay; the winner leaves the group when none are left). Review and publish it, then delete the experiment.
Multi-page test: list several variant groups (one per line) in one block experiment, e.g. the same message on the landing page, the product page and the checkout. A visitor gets the same letter in every group, so give each group the same number of variants: while the test runs, the experiment page warns when the groups on its pages have different numbers or a group isn't found. Add every page under Pages for preview links and Apply winner to pages (one draft per page).
Document type test: choose the type Document type, a document type, and up to five variants (B, C, ...), each with another of the type's allowed templates and/or CSS and JavaScript; variant A is the page as it is. Visitors in the test get every page of that type rendered with their variant's template, with its CSS in the page head and its JavaScript at the end of the page. A template must be allowed on the document type first (Settings > Document Types > Templates). Previews, audience, traffic, scheduling and results work as for block tests. The code runs on your live site: try it with the preview links first. When finished with a winner, everyone gets its code until you move it into the site's stylesheet or scripts and delete the experiment.
Projects: give experiments a project name to group them; the list then has a project filter.
Split-URL test: in Modeto > Experiments, choose the type Split URL, page A (the control) and up to five variant pages. Visitors in the test who are assigned B, C, ... are redirected (302) from page A to their page, with the query string kept (campaign parameters still count); each visitor always gets the same page. Visitors without A/B consent, bots and visitors outside the audience/traffic stay on page A. Preview links (
?modeto_preview=<test>:Bon page A) redirect for anyone. Finished with a winner other than A, everyone opening page A is redirected to it; update links and menus, then delete the experiment. While the test runs, variant pages are sent withX-Robots-Tag: noindex(they duplicate page A); hide them from menus too. A page can't be in two running split-URL tests at once (as control or variant).Analytics: Modeto > Analytics (date range, by day/week/month, a table view of the chart), and the Analytics tab on a content item for that page. Past days come from daily summaries built every hour; today is always live. Page and event rows count visits (sessions); visitor totals are distinct visitors.
Custom events and goals from the page (the client script is added for you):
modeto.track('Video', 'Play', 'Product tour'); // category, action, optional label modeto.goal('newsletterSignup'); // goal alias (trigger "code")Outbound link and download clicks are tracked as events automatically.
Visitors: Modeto > Visitors lists profiles (filter by members only, device, audience, visits, last seen; Export CSV). A profile shows the member (when the visitor logged in), engaged time, what Modeto remembers (pages, campaigns, events, goals) and the latest visits as a timeline. A member who logged in on several devices or browsers is one person there: one row in the list (their latest device, with visits and page views combined), and the profile lists those devices and combines their visits, engagement and goals (what Modeto remembers for audiences stays per device), and Erase visitor erases all of them. Download data saves it as JSON for data-access requests; Erase visitor deletes it.
Goals from server code: inject
IModetoGoalsand callComplete(HttpContext, "newsletterSignup"). Does nothing for visitors without consent.?modeto_audience=<alias>on any page previews a variant for an audience (not tracked, not cached).Modeto > Traffic: rules for IP addresses/CIDR ranges and user agents. Matching traffic is treated like a bot (no cookies, not tracked, default content). Behind a proxy or CDN, configure ASP.NET Core forwarded headers so Modeto sees the real client IP. The same page lists visitors with many page views; Mark as bot removes them from lists and experiment results and stops recording them.
Good to know
- Modeto stores no IP addresses; referrers are kept as host name only. Cookies:
modeto_v(visitor state, 365 days) andmodeto_s(visit; a visit ends after 30 minutes without a page view), both first-party and HttpOnly, andmodeto_pv(current page view id for the client script, 5 minutes) andmodeto_a(activated audiences, script-readable, session; marketing consent only). All set only with consent.modeto_vremembers the latest 20 pages, campaigns, sources, media, sections, events and goals (oldest dropped first to stay under the cookie size limit). The client script posts to/__modeto/collect, which also checks consent. - Personalized and A/B-tested pages send
Cache-Control: private, no-store. Don't put them behind a shared CDN cache. - Caches (audiences, running experiments, block rules, pages) are in memory per server and cleared on every server when they change (Umbraco's distributed cache).
- Members: Modeto reads Umbraco's member login (ASP.NET Core Identity application cookie) on tracked pages and stores the member key on the visitor. Logged-in pages are never page-cached.
- The Delivery API (headless) returns blocks unpersonalized.
- Personalization works when a template reads the block value in a request (
foreach,Html.GetBlockGridHtmlAsync, ...). ThePersonalize()extension is still available for custom code and is safe to call on already-personalized values.
Showing the top 20 packages that depend on Modeto.Intelligence.
| Packages | Downloads |
|---|---|
|
Modeto.Intelligence.Forms
Umbraco Forms support for Modeto Intelligence: form funnels (views, starts, submissions, drop-off by field), a "form submitted" goal trigger, and visitor identification from form fields.
|
1 |
|
Modeto.Intelligence.Forms
Umbraco Forms support for Modeto Intelligence: form funnels (views, starts, submissions, drop-off by field), a "form submitted" goal trigger, and visitor identification from form fields.
|
0 |
.NET 10.0
- MaxMind.GeoIP2 (>= 6.1.0)
- Umbraco.Cms.Api.Management (>= 18.2.1 && < 19.0.0)
- Umbraco.Cms.Web.Website (>= 18.2.1 && < 19.0.0)