{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-11-09T11:26:38Z","object_id":{"id":3149,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_analytics","title":"mod_analytics","uri":"https:\/\/zotonic.com\/id\/3149"},"seq":1}],"predicate":{"id":333,"is_a":["meta","predicate"],"name":"in_module","title":{"_type":"trans","tr":{"en":"In module"}},"uri":"https:\/\/zotonic.com\/id\/in_module"}},"subject":{"objects":[{"created":"2026-11-09T11:26:38Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-11-09T11:26:38Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":2},{"created":"2026-11-09T11:26:38Z","object_id":{"id":2592,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_authorization_and_access_control","title":"Authorization and access control","uri":"https:\/\/zotonic.com\/id\/2592"},"seq":3},{"created":"2026-11-09T11:26:38Z","object_id":{"id":2593,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_privacy_and_consent","title":"Privacy and consent","uri":"https:\/\/zotonic.com\/id\/2593"},"seq":4},{"created":"2026-11-09T11:26:38Z","object_id":{"id":2607,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_logging_and_monitoring","title":"Logging and monitoring","uri":"https:\/\/zotonic.com\/id\/2607"},"seq":5},{"created":"2026-11-09T11:26:38Z","object_id":{"id":2625,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_model","title":"Model","uri":"https:\/\/zotonic.com\/id\/2625"},"seq":6}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"export_revision":21,"id":3152,"is_a":["text","documentation","reference","model"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3152"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_model_model_analytics","page_url":{"en":"https:\/\/zotonic.com\/docs\/3152\/analytics","x-default":"https:\/\/zotonic.com\/docs\/3152\/analytics"},"preview_url":null,"resource":{"version":7,"pivot_location_lat":null,"title":"analytics","is_authoritative":true,"body":"<p>Read Site Analytics reports, manage recordings and submit trusted server-side events.<\/p>\n<h2>Model paths<\/h2>\n<table class=\"table\"><thead><tr><th>GET path<\/th><th>Result<\/th><th>Permission<\/th><\/tr><\/thead><tbody><tr><td><code>recordings<\/code><\/td><td>Recording maps in saved order.<\/td><td><code>use mod_analytics<\/code><\/td><\/tr><tr><td><code>overview<\/code><\/td><td>Map with <code>panels<\/code> and <code>period<\/code> for non-paused recordings.<\/td><td><code>use mod_analytics<\/code><\/td><\/tr><tr><td><code>dashboard<\/code><\/td><td>Recordings, selected recording and its report.<\/td><td><code>use mod_analytics<\/code><\/td><\/tr><tr><td><code>configuration<\/code><\/td><td>Recordings, event schemas, dimensions, status and storage usage.<\/td><td>Site administrator<\/td><\/tr><tr><td><code>status<\/code><\/td><td>Current ingestion worker status.<\/td><td>Site administrator<\/td><\/tr><tr><td><code>registry<\/code><\/td><td>List of declared event schemas.<\/td><td>Site administrator<\/td><\/tr><tr><td><code>info\/&lt;id&gt;<\/code><\/td><td>Recording details and stored payload\/bucket usage.<\/td><td>Site administrator<\/td><\/tr><\/tbody><\/table>\n<p>For example, <code>{% for recording in m.analytics.recordings %}{{ recording.name|escape }}{% endfor %}<\/code>\nlists the configured recordings. Model GET callbacks return <code>{ok, {Value, Rest}}<\/code>.\nAccess failures return <code>{error, eacces}<\/code>; unsupported paths return <code>{error, unknown_path}<\/code>.\nSite administrator checks use <code>z_acl:is_admin\/1<\/code>, including <code>use mod_admin_config<\/code>.<\/p>\n<p>POST <code>create<\/code> accepts a definition map; <code>state\/&lt;id&gt;<\/code> accepts a map with binary key\n<code>state<\/code> (<code>active<\/code> or <code>paused<\/code>); <code>order<\/code> accepts the full list of recording IDs.\nThese operations enforce administrator access. Deletion is available through <code>purge\/2<\/code>\nand the configuration page&#39;s confirmation flow, not a public model POST path.<\/p>\n<h2>Erlang reporting and configuration<\/h2>\n<p><code>overview(Period, Context)<\/code> returns all non-paused recording panels; <code>dashboard(Id, Period, Context)<\/code>\nreturns one selected report with the recording list. Both enforce <code>use mod_analytics<\/code>.\nPeriod keys are <code>hour<\/code>, <code>day<\/code> (six hours), <code>24hours<\/code>, <code>week<\/code>, <code>month<\/code>, <code>year<\/code> and <code>years<\/code>.\nUnknown periods use six hours. Reports contain aggregate counts; popular-content counts\nare lower bounds with approximation metadata. <code>configuration\/1<\/code> enforces administrator access.<\/p>\n<p><code>create(Definition, State, Context)<\/code> accepts binary keys <code>name<\/code>, optional <code>description<\/code>,\n<code>event<\/code>, <code>dimension<\/code>, <code>groups<\/code> and <code>filters<\/code>. Filters map dimension names to lists of\nallowed values; an empty dimension records totals only. Initial state can be <code>active<\/code>,\n<code>paused<\/code> or <code>draft<\/code>. Success returns <code>{ok, Id}<\/code>; invalid definitions, duplicate recordings\nand exhausted storage budgets return errors. <code>set_state\/3<\/code>, <code>reorder\/2<\/code> and <code>purge\/2<\/code>\nalso enforce administrator access. Purging permanently deletes a recording and its history.<\/p>\n<h2>Trusted event API<\/h2>\n<p>After declaring an event in an <code>observe_analytics_events\/3<\/code> fold observer, submit it with:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">m_analytics:record_event(&lt;&lt;&quot;mod_newsletter:sent&quot;&gt;&gt;,\n    #{&lt;&lt;&quot;kind&quot;&gt;&gt; =&gt; &lt;&lt;&quot;digest&quot;&gt;&gt;}, Context).\n<\/code><\/pre>\n<p><code>record_event\/3<\/code> does not implicitly associate the context&#39;s request. For request-associated\nmeasurement use <code>record_event(Name, Properties, z_context:get_reqdata(Context), Context)<\/code>.\nThe request must match the context. Request exclusions apply, and schema-declared request\ndimensions are added by the server; callers cannot override these derived properties.\nThese are trusted Erlang APIs, not public model POST routes or administrator-only actions.\nThe calling module is responsible for its domain permissions and for avoiding personal data.\n<code>registry\/1<\/code> is an internal Erlang schema lookup without the model GET permission check.\nThe <code>store_*<\/code> functions are trusted persistence APIs used by schema installation and\nbackground workers; they do not enforce user ACLs and are not exposed as model routes.<\/p>\n<p>Recording returns <code>ok<\/code> or <code>{error, Reason}<\/code>. Excluded requests return <code>ok<\/code> without recording;\nan accepted event is not a durable-write acknowledgement. Invalid requests, unknown events,\ninvalid properties and full ingestion queues can return errors. Analytics failure should\nnot fail the domain operation. <code>mark_page(Id, Context)<\/code> returns an updated context marking\na custom controller&#39;s page; normal public-page eligibility checks still apply.<\/p>","slug":"analytics","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"0e3e5523ab38990b652b19dea528763f4dc9375f2fccc3c7017c0c0da12e30c7","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"category_id":{"id":322,"is_a":["meta","category"],"name":"model","title":"Models","uri":"https:\/\/test.zotonic.com\/id\/322"},"doc_source_path":"apps\/zotonic_mod_analytics\/src\/models\/m_analytics.erl","publication_start":"2026-11-09T11:26:38Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_analytics\/src\/models\/m_analytics.erl","pivot_location_lng":null,"doc_source_kind":"model","name":"doc_model_model_analytics","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-11-09T11:26:38Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_analytics","publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"f3c6a9a2dde17cb5b121dd3e3018d9ef4b1b3943\n","erlang_module":"m_analytics","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-11-09T21:39:43Z","title_slug":"analytics"},"uri":"https:\/\/zotonic.com\/id\/3152","uri_template":"https:\/\/zotonic.com\/id\/:id","version":21,"websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3152"}},"status":"ok"}