{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-11-11T09:19:20Z","object_id":{"id":1457,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_development","title":"mod_development","uri":"https:\/\/zotonic.com\/id\/1457"},"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"}},"refers":{"objects":[{"created":"2026-11-11T12:17:46Z","object_id":{"id":1457,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_development","title":"mod_development","uri":"https:\/\/zotonic.com\/id\/1457"},"seq":1000000},{"created":"2026-11-11T12:17:46Z","object_id":{"id":3182,"is_a":["text","documentation","reference","model"],"name":"doc_model_model_development_tools","title":"development_tools","uri":"https:\/\/zotonic.com\/id\/3182"},"seq":1000000}],"predicate":{"id":2409,"is_a":["meta","predicate"],"name":"refers","title":{"_type":"trans","tr":{"en":"Refers"}},"uri":"https:\/\/zotonic.com\/id\/refers"}},"subject":{"objects":[{"created":"2026-11-11T09:19:20Z","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-11T09:19:20Z","object_id":{"id":2563,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_frontend_developer","title":"Frontend developer","uri":"https:\/\/zotonic.com\/id\/2563"},"seq":2},{"created":"2026-11-11T09:19:20Z","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":3},{"created":"2026-11-11T09:19:20Z","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":4},{"created":"2026-11-11T09:19:20Z","object_id":{"id":2613,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_development_and_debugging","title":"Development and debugging","uri":"https:\/\/zotonic.com\/id\/2613"},"seq":5},{"created":"2026-11-11T09:19:20Z","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":24,"id":3183,"is_a":["text","documentation","reference","model"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3183"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_model_model_development_capture","page_url":{"en":"https:\/\/zotonic.com\/docs\/3183\/development_capture","x-default":"https:\/\/zotonic.com\/docs\/3183\/development_capture"},"preview_url":null,"resource":{"version":8,"pivot_location_lat":null,"title":"development_capture","is_authoritative":true,"body":"<p>Capture template rendering and browser console output from an open page without\nlosing its current state. Use this model with <a href=\"\/id\/doc_model_model_development_tools\" class=\"doc-reference doc-reference-model\"><code>model#development_tools<\/code><\/a> to inspect\nhow a page is built and verify conditional or lazy rendering.<\/p>\n<p>For local and remote workflows, see the\n<a href=\"..\/..\/..\/..\/doc\/guides\/llm-site-development.md\">LLM site development guide<\/a>.<\/p>\n<h2>Access and setup<\/h2>\n<p>Enable <a href=\"\/id\/doc_module_mod_development\" class=\"doc-reference doc-reference-module\"><code>module#mod_development<\/code><\/a> on the site. All data and mutation endpoints\nrequire an administrator with <code>use mod_development<\/code> permission and a site\nenvironment of <code>development<\/code>, <code>test<\/code>, or <code>acceptance<\/code>. Production is denied,\nincluding trusted CLI calls. The <code>console_allowed<\/code> capability probe returns a\nboolean rather than an access error.<\/p>\n<p>Read-only administrators can list captures and read their status, traces, and\nlogs. Attaching, starting, stopping, and submitting logs require write access.\nAttaching to an existing capture and submitting logs also require the browser\nsession that created it. Session IDs are never returned.<\/p>\n<p>The browser API is <code>window.z_development_capture<\/code>. Its script is included on\nauthorized pages when the floating template trace button is enabled. Use the\nnormal development head assets; the button and JavaScript use the same API.<\/p>\n<h2>Capture an open page<\/h2>\n<p>Run this in the intended page&#39;s browser console or browser execution tool:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-javascript\">const capture = await window.z_development_capture.attach();\n\/\/ Interact with the page: scroll, open a panel, or trigger a lazy component.\nawait window.z_development_capture.flush();\nconst trace = await window.z_development_capture.trace();\nconst logs = await window.z_development_capture.console();\nawait window.z_development_capture.stop();\n<\/code><\/pre>\n<p><code>trace<\/code> contains <code>{capture, nodes, edges}<\/code>. <code>logs<\/code> contains\n<code>{entries, truncated}<\/code>; each entry has <code>seq<\/code>, <code>text<\/code>, and <code>received_at<\/code>.\nRead results through the execution tool&#39;s return value rather than logging them\nback into the capture. Reads do not submit or clear browser logs.<\/p>\n<h2>JavaScript reference<\/h2>\n<p>All methods return promises. Permission and transport failures reject; model\nerrors expose <code>error.code<\/code>. An optional ID defaults to the page&#39;s attachment.<\/p>\n<table class=\"table\"><thead><tr><th>Method<\/th><th>Behavior<\/th><\/tr><\/thead><tbody><tr><td><code>list()<\/code><\/td><td>GET all available capture summaries; returns <code>[]<\/code> when none exist.<\/td><\/tr><tr><td><code>status(captureId?)<\/code><\/td><td>GET a capture summary.<\/td><\/tr><tr><td><code>trace(captureId?)<\/code><\/td><td>GET the accumulated template graph.<\/td><\/tr><tr><td><code>console(captureId?)<\/code><\/td><td>GET stored console entries without flushing pending logs.<\/td><\/tr><tr><td><code>attach()<\/code><\/td><td>Reuse the page&#39;s attachment or create one; start it if stopped.<\/td><\/tr><tr><td><code>attach(captureId)<\/code><\/td><td>Validate write access and session ownership, then reconnect and start if stopped.<\/td><\/tr><tr><td><code>start(captureId)<\/code><\/td><td>Start an existing capture; an ID is required.<\/td><\/tr><tr><td><code>stop()<\/code><\/td><td>Flush this page&#39;s pending logs, then stop its capture.<\/td><\/tr><tr><td><code>stop(captureId)<\/code><\/td><td>Stop an existing capture without flushing this page&#39;s logs.<\/td><\/tr><tr><td><code>flush()<\/code><\/td><td>Submit this page&#39;s pending logs without starting or stopping its capture.<\/td><\/tr><tr><td><code>detach()<\/code><\/td><td>Clear the local attachment and pending logs, restore <code>console.log<\/code>, and leave the server capture running.<\/td><\/tr><\/tbody><\/table>\n<p>Status and evidence reads return <code>null<\/code> when the capture is missing or expired,\nor when no page attachment exists. Explicit-ID start\/stop also return <code>null<\/code>\nfor a missing capture. Explicit-ID reads and start\/stop do not change the local\nattachment or sessionStorage, including on errors.<\/p>\n<p><code>attach(captureId)<\/code> rejects missing, expired, or foreign-session IDs; it never\ncreates a replacement. Validation and start must succeed before the ID is saved.\nSwitching IDs discards the previous browser buffer; call <code>flush()<\/code> first to retain\nit. Reconnecting the same ID preserves that buffer.<\/p>\n<p><code>flush()<\/code> returns status, or <code>null<\/code> when unattached or expired. It does not submit\nto a stopped capture. <code>detach()<\/code> returns <code>null<\/code>, makes no network request, and\nworks offline and with read-only access. Queued local commands and late replies\ncannot reattach a detached page; already-sent server requests may still complete.\nDo not automatically retry mutations after a timeout.<\/p>\n<h2>Ownership and capture summaries<\/h2>\n<p>List and status results contain these fields:<\/p>\n<table class=\"table\"><thead><tr><th>Field<\/th><th>Meaning<\/th><\/tr><\/thead><tbody><tr><td><code>id<\/code><\/td><td>Capture ID used by every interface.<\/td><\/tr><tr><td><code>path<\/code><\/td><td>Page path recorded at attachment; tracing is session-scoped, not restricted to this path.<\/td><\/tr><tr><td><code>state<\/code><\/td><td><code>active<\/code> or <code>stopped<\/code>.<\/td><\/tr><tr><td><code>expires<\/code><\/td><td>Absolute expiry time, in Unix seconds.<\/td><\/tr><tr><td><code>truncated<\/code><\/td><td>A graph or server console limit has been reached.<\/td><\/tr><tr><td><code>trace_scope<\/code><\/td><td><code>session<\/code>. Other tabs in the session may contribute renders.<\/td><\/tr><tr><td><code>is_owner<\/code><\/td><td>Whether this request has the browser session that created the capture. False without a session.<\/td><\/tr><tr><td><code>write_allowed<\/code><\/td><td>Whether the caller may mutate captures; independent of ownership.<\/td><\/tr><tr><td><code>console_allowed<\/code><\/td><td>Whether the caller may access console diagnostics.<\/td><\/tr><\/tbody><\/table>\n<p>To find a capture this browser can reconnect to:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-javascript\">const captures = await window.z_development_capture.list();\nconst owned = captures.find(c =&gt; c.is_owner &amp;&amp; c.write_allowed);\nif (owned) {\n    await window.z_development_capture.attach(owned.id);\n}\n<\/code><\/pre>\n<p>Read-only owners cannot attach or submit logs. Writable non-owners can start or\nstop captures by ID but cannot attach or submit browser logs. Server checks are\nauthoritative; the flags help clients select the right operation.<\/p>\n<p>The browser requires <code>is_owner === true<\/code> before wrapping <code>console.log<\/code> or\nsubmitting logs. Local attach\/flush\/stop and background polling refresh status\nfirst. Lost or missing ownership restores normal console output and discards\npending logs. Local mutations reject with <code>eacces<\/code>; background polling uses GET\nonly. Use <code>detach()<\/code> to clear a stale local attachment.<\/p>\n<h2>HTTP and MQTT endpoints<\/h2>\n<p>HTTP uses <code>\/api\/model\/development_capture\/&lt;method&gt;\/&lt;path&gt;<\/code>. Browser MQTT calls\nuse <code>bridge\/origin\/model\/development_capture\/&lt;method&gt;\/&lt;path&gt;<\/code>; a direct server\nMQTT client uses <code>model\/development_capture\/&lt;method&gt;\/&lt;path&gt;<\/code>.<\/p>\n<table class=\"table\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Payload<\/th><th>Result<\/th><\/tr><\/thead><tbody><tr><td>GET<\/td><td><code>console_allowed<\/code><\/td><td>None<\/td><td>Capability boolean.<\/td><\/tr><tr><td>GET<\/td><td><code>list<\/code><\/td><td>None<\/td><td>Array of capture summaries.<\/td><\/tr><tr><td>GET<\/td><td><code>status\/ID<\/code><\/td><td>None<\/td><td>Capture summary.<\/td><\/tr><tr><td>GET<\/td><td><code>trace\/ID<\/code><\/td><td>None<\/td><td><code>{capture, nodes, edges}<\/code>.<\/td><\/tr><tr><td>GET<\/td><td><code>console\/ID<\/code><\/td><td>None<\/td><td><code>{entries, truncated}<\/code>.<\/td><\/tr><tr><td>POST<\/td><td><code>attach<\/code><\/td><td><code>{path: &quot;\/example&quot;}<\/code><\/td><td>New, stopped capture bound to the caller&#39;s browser session.<\/td><\/tr><tr><td>POST<\/td><td><code>start\/ID<\/code><\/td><td>None<\/td><td>Active capture summary; existing evidence is retained.<\/td><\/tr><tr><td>POST<\/td><td><code>stop\/ID<\/code><\/td><td>None<\/td><td>Stopped capture summary.<\/td><\/tr><tr><td>POST<\/td><td><code>poll\/ID<\/code><\/td><td><code>{entries: [&quot;message&quot;]}<\/code><\/td><td>Submit logs to an active capture and return status; requires the owning session.<\/td><\/tr><\/tbody><\/table>\n<p>HTTP JSON and MQTT response payloads use <code>{status: &quot;ok&quot;, result: ...}<\/code> or\n<code>{status: &quot;error&quot;, error: ...}<\/code>. Raw model endpoints return <code>enoent<\/code> for missing\nor expired captures; JavaScript methods translate it to <code>null<\/code> where described\nabove. <code>eacces<\/code> denotes denied access; invalid arguments or a foreign-session\npoll can return <code>badarg<\/code>.<\/p>\n<p>An OAuth2 administrator token can inspect existing captures and, with write\naccess, start\/stop them. A token without the owning browser session cannot\nattach to that capture or submit its console logs. The browser normally handles\nattachment and polling; CLI\/API control alone does not collect browser output.<\/p>\n<h2>CLI and SSH<\/h2>\n<p>Run from the server&#39;s Zotonic checkout as its normal server user:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-sh\">bin\/zotonic capture mysite list\nbin\/zotonic capture mysite status CAPTURE_ID\nbin\/zotonic capture mysite start CAPTURE_ID\nbin\/zotonic capture mysite trace CAPTURE_ID\nbin\/zotonic capture mysite console CAPTURE_ID\nbin\/zotonic capture mysite stop CAPTURE_ID\n<\/code><\/pre>\n<p>For remote access, run the same commands through SSH:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-sh\">ssh site-host &#39;cd \/srv\/zotonic &amp;&amp; bin\/zotonic capture mysite list&#39;\n<\/code><\/pre>\n<p>CLI commands use trusted Erlang node RPC and still enforce the environment\nrestriction. They return JSON <code>{ok: true, result: ...}<\/code> or\n<code>{ok: false, error: ...}<\/code>, with a nonzero exit status for failures.<\/p>\n<h2>Lifetime and limits<\/h2>\n<ul><li>A capture expires ten minutes after creation. Polling and restarting do not\nextend it. Stopping retains evidence until expiry.<\/li><li>At most 16 captures exist per site. Attach a fresh capture for a fresh graph;\nstarting an existing one is idempotent and retains evidence.<\/li><li>The graph is capped at approximately 1,000 nodes and 5,000 edges. Console\nhistory retains the latest 500 entries, up to 4,096 bytes each and 50 per upload.<\/li><li>Browser console collection covers subsequent <code>console.log<\/code> calls only. It is\nbest effort: overflow, navigation, disconnection, or remote stopping can lose logs.<\/li><li>The browser polls every second. Attachment survives reload via sessionStorage;\nrenders before attachment cannot be reconstructed. Attach before reloading to\ntrace the initial render. Duplicated tabs may inherit the stored attachment.<\/li><li>This API provides template traces and console text, not screenshots, DOM\nsnapshots, historical console output, or browser action execution.<\/li><\/ul>\n<p>Existing human template tracing remains independent of capture sessions.<\/p>","slug":"development_capture","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"d6035bba66bf8e303125b5a224c0002f1d6e711129695e6df50c98d678dc4934","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_development\/src\/models\/m_development_capture.erl","publication_start":"2026-11-11T09:19:20Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_development\/src\/models\/m_development_capture.erl","pivot_location_lng":null,"doc_source_kind":"model","name":"doc_model_model_development_capture","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-11-11T09:19:20Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_development","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":"8e6aad17c4d86dee983db46a656ed6f6a930bcbb\n","erlang_module":"m_development_capture","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-11-11T14:07:16Z","title_slug":"development_capture"},"uri":"https:\/\/zotonic.com\/id\/3183","uri_template":"https:\/\/zotonic.com\/id\/:id","version":24,"websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3183"}},"status":"ok"}