{"result":{"depiction_url":null,"edges":{"observes":{"objects":[{"created":"2026-09-09T13:47:47Z","object_id":{"id":1442,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_request_context","title":"observe_request_context\/3","uri":"https:\/\/zotonic.com\/id\/1442"},"seq":1}],"predicate":{"id":2550,"is_a":["meta","predicate"],"name":"observes","title":{"_type":"trans","tr":{"en":"Observes"}},"uri":"https:\/\/zotonic.com\/id\/observes"}},"subject":{"objects":[{"created":"2026-09-09T13:47:47Z","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-09-09T13:47:47Z","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-09-09T13:47:47Z","object_id":{"id":2631,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_database","title":"Database","uri":"https:\/\/zotonic.com\/id\/2631"},"seq":3},{"created":"2026-09-09T13:47:47Z","object_id":{"id":2635,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_module","title":"Module","uri":"https:\/\/zotonic.com\/id\/2635"},"seq":4},{"created":"2026-09-09T13:47:47Z","object_id":{"id":2638,"is_a":["categorization","keyword","keyword_task"],"name":"zotonic_topic_edit","title":"Edit","uri":"https:\/\/zotonic.com\/id\/2638"},"seq":5},{"created":"2026-09-09T13:47:47Z","object_id":{"id":2643,"is_a":["categorization","keyword","keyword_task"],"name":"zotonic_topic_query","title":"Query","uri":"https:\/\/zotonic.com\/id\/2643"},"seq":6},{"created":"2026-09-09T13:47:47Z","object_id":{"id":2665,"is_a":["categorization","keyword","keyword_data_type"],"name":"zotonic_topic_structured_data","title":"Structured data","uri":"https:\/\/zotonic.com\/id\/2665"},"seq":7}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":1307,"is_a":["text","documentation","reference","module"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1307"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_module_mod_server_storage","page_url":{"en":"https:\/\/zotonic.com\/docs\/1307\/mod_server_storage","x-default":"https:\/\/zotonic.com\/docs\/1307\/mod_server_storage"},"preview_url":null,"resource":{"version":3988,"pivot_location_lat":null,"title":"mod_server_storage","is_authoritative":true,"body":"<p>Server side storage for the client (aka browser) and server.<\/p>\n<p>The storage is tied to the <code>sid<\/code> in the authentication token of the client. This <code>sid<\/code> is unique and changed if the user\nlogs on or logs off.<\/p>\n<h2>Model functions<\/h2>\n<p><code>model\/server_storage\/post\/key<\/code> store a key, with the value of in the payload of the message.<\/p>\n<p><code>model\/server_storage\/get\/key<\/code> - fetch a key.<\/p>\n<p><code>model\/server_storage\/get<\/code> - fetch all keys.<\/p>\n<p><code>model\/server_storage\/delete\/key<\/code> - delete a key.<\/p>\n<p><code>model\/server_storage\/delete<\/code> - delete all values from the server storage.<\/p>\n<p>Example in JavaScript from the client:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-javascript\">cotonic.broker.publish(&quot;bridge\/origin\/model\/server_storage\/post\/foo&quot;, { &quot;hello&quot;: &quot;world&quot; });\n\ncotonic.broker.call(&quot;bridge\/origin\/model\/server_storage\/get\/foo&quot;)\n       .then( (msg) =&gt; console.log(msg) );\n\n{\n    dup: false\n    packet_id: null\n    payload: {result: {hello: &quot;world&quot;}, status: &quot;ok&quot;}\n    properties: {content_type: &quot;application\/json&quot;}\n    qos: 0\n    retain: false\n    topic: &quot;reply\/page-3-0.4080048813145437&quot;\n    type: &quot;publish&quot;\n}\n<\/code><\/pre>\n<p>Example in a template:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">The hello key of foo is now: {{ m.server_storage.foo.hello|escape }}\n<\/code><\/pre>\n<p>Note that we <strong>must<\/strong> escape the value, as it originates from the client and contains arbitrary values, including HTML.<\/p>\n<p>Example in Erlang:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">m_server_storage:store(&lt;&lt;&quot;foo&quot;&gt;&gt;, #{ &lt;&lt;&quot;hello&quot;&gt;&gt; =&gt; &lt;&lt;&quot;world&quot;&gt;&gt; }, Context),\n\ncase m_server_storage:lookup(&lt;&lt;&quot;foo&quot;&gt;&gt;, Context) of\n    {ok, Value} -&gt;  % Found the value\n    {error, no_session} -&gt; % No session\n    {error, not_found} -&gt; % There is a session but unknown key\nend.\n\nm_server_storage:delete(&lt;&lt;&quot;foo&quot;&gt;&gt;, Context).\n<\/code><\/pre>\n<h2>Storage of secret server data<\/h2>\n<p>Sometimes the server code wants to attach data to the specific client that is not accessible to the client itself. An\nexample is a secret during an OAuth handshake.<\/p>\n<p>For this there is a special, unlimited, storage API, which is only accessible using the Erlang API:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">m_server_storage:secure_store(&lt;&lt;&quot;foo&quot;&gt;&gt;, #{ &lt;&lt;&quot;my&quot;&gt;&gt; =&gt; &lt;&lt;&quot;secret&quot;&gt;&gt; }, Context),\n\ncase m_server_storage:secure_lookup(&lt;&lt;&quot;foo&quot;&gt;&gt;, Context) of\n    {ok, Value} -&gt;  % Found the value\n    {error, no_session} -&gt; % No session\n    {error, not_found} -&gt; % There is a session but unknown key\nend.\n\nm_server_storage:secure_delete(&lt;&lt;&quot;foo&quot;&gt;&gt;, Context).\n<\/code><\/pre>\n<p>The keys in the <em>secure<\/em> storage are separate from the keys in the normal storage. There can be a key with the same name\nand different values in both storages.<\/p>\n<h2>Storage process<\/h2>\n<p>If a value is stored in the server side storage then a process is started. This process holds all stored values, and\ntimes out after 900 seconds.<\/p>\n<p>Any HTTP request with the proper <code>sid<\/code> will extend the lifetime of the storage process.<\/p>\n<p>The storage process holds at most 100 KB of data, above that it will respond with a <code>full<\/code> error status.<\/p>\n<h2>Configuration keys<\/h2>\n<p>There are two configuration keys, both need a number:<\/p>\n<ul><li><code>mod_server_storage.storage_expire<\/code> The timeout in seconds of the storage process defaults to 900 seconds.<\/li><li><code>mod_server_storage.storage_maxsize<\/code> The maximum stored size in bytes, both the keys and the values are counted. Default is 100 KB.<\/li><\/ul>\n<p>Note that the storage is in-memory, so it is best to set the storage maxsize and expire as low as possible.<\/p>\n<h2>Accepted Events<\/h2>\n<p>This module handles the following notifier callbacks:<\/p>\n<ul><li><code>observe_request_context<\/code>: Periodic ping for the session, done by the client using <code>m_server_storage:ping<\/code>.<\/li><li><code>observe_server_storage<\/code>: Decoupling of server storage from other modules using <code>m_server_storage:lookup<\/code>.<\/li><\/ul>","slug":"mod_server_storage","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"7d6381e40e11ef7a2abff28accc0c04b0cc0fc4e5a71e9ea63b22790df998878","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":320,"is_a":["meta","category"],"name":"module","title":"Modules","uri":"https:\/\/test.zotonic.com\/id\/320"},"doc_source_path":"apps\/zotonic_mod_server_storage\/src\/mod_server_storage.erl","publication_start":"2023-06-24T08:17:53Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_server_storage\/src\/mod_server_storage.erl","pivot_location_lng":null,"doc_source_kind":"module","name":"doc_module_mod_server_storage","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2020-05-30T05:47:18Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_server_storage","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":"b8c4b2ccdc223257f311968fbd17b2d66f0c4156\n","creator_id":{"id":336,"is_a":["person","robot"],"name":"gitbot","title":"Git","uri":"https:\/\/zotonic.com\/id\/336"},"modified":"2026-09-30T09:39:38Z","title_slug":"mod_server_storage"},"uri":"https:\/\/zotonic.com\/id\/1307","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1307"}},"status":"ok"}