{"result":{"depiction_url":null,"edges":{"refers":{"objects":[{"created":"2026-11-05T18:42:37Z","object_id":{"id":1329,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_filestore","title":"mod_filestore","uri":"https:\/\/zotonic.com\/id\/1329"},"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-05T18:42:37Z","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-05T18:42:37Z","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-05T18:42:37Z","object_id":{"id":2576,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_media_management","title":"Media management","uri":"https:\/\/zotonic.com\/id\/2576"},"seq":3},{"created":"2026-11-05T18:42:37Z","object_id":{"id":2581,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_file_storage","title":"File storage","uri":"https:\/\/zotonic.com\/id\/2581"},"seq":4},{"created":"2026-11-05T18:42:37Z","object_id":{"id":2627,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_notification","title":"Notification","uri":"https:\/\/zotonic.com\/id\/2627"},"seq":5}],"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":3138,"is_a":["text","documentation","reference","notification"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3138"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_notification_filestore","page_url":{"en":"https:\/\/zotonic.com\/docs\/3138\/observe_filestore-2","x-default":"https:\/\/zotonic.com\/docs\/3138\/observe_filestore-2"},"preview_url":null,"resource":{"version":12,"pivot_location_lat":null,"title":"observe_filestore\/2","is_authoritative":true,"body":"<p>Resolve managed file storage operations. Records are defined in <code>zotonic_file.hrl<\/code>.<\/p>\n<p>Type: <a href=\"\/id\/doc_developerguide_notifications#notification-first\">first<\/a>.<\/p>\n<p><code>#filestore{}<\/code> properties:<\/p>\n<ul><li><code>action<\/code>: <code>lookup<\/code>, <code>exists<\/code>, <code>upload<\/code>, <code>delete<\/code>, <code>delete_status<\/code>, <code>cancel_delete<\/code>, <code>purge_previews<\/code>, <code>cafs_adopt<\/code>, or <code>cafs_retire<\/code>.<\/li><li><code>path<\/code>: path relative to the site&#39;s files directory, such as <code>archive\/example.jpg<\/code>.\nDeletion and deletion-status requests also accept <code>{prefix, Prefix}<\/code>.<\/li><li><code>local_path<\/code>: optional local filename used by <code>lookup<\/code> to prefer a retained local copy.<\/li><li><code>mime<\/code>: optional MIME type for upload queue metadata.<\/li><li><code>target_path<\/code>: canonical CAFS path for <code>cafs_adopt<\/code> and <code>cafs_retire<\/code>.<\/li><li><code>older_than<\/code>: timestamp text used by <code>purge_previews<\/code> as a fixed cutoff.<\/li><li><code>filename<\/code>: optional filename; the standard filestore handler does not use this field.<\/li><\/ul>\n<p>Return values depend on the action:<\/p>\n<ul><li><code>lookup<\/code>: <code>{ok, {filename, Filename, StoreEntry}}<\/code> or\n<code>{ok, {filezcache, Pid, StoreEntry}}<\/code>. <code>StoreEntry<\/code> is a map of backend metadata.\nA cache miss may start a download. Return <code>undefined<\/code> to fall back to local storage\nor another observer; it does not establish that the remote file is absent.<\/li><li><code>exists<\/code>: read-only registry check, without network I\/O or cache writes. Return\n<code>true<\/code> for a non-deleted entry without a recorded read error, <code>false<\/code> if absent,\nor <code>{error, Reason}<\/code> if availability is uncertain. This is not a remote probe.<\/li><li><code>upload<\/code>: <code>ok<\/code> after queueing the upload, or <code>{error, Reason}<\/code>. This does not\ntransfer file bytes. Queue insertion must participate in the caller&#39;s transaction.<\/li><li><code>delete<\/code>: <code>ok<\/code> after marking matching entries for delayed deletion, or an error.\nThis does not confirm remote absence. Repeated requests must preserve retention.<\/li><li><code>cafs_adopt<\/code>: register an existing remote original at <code>target_path<\/code> without\ndownloading it. Return <code>ok<\/code> or <code>{error, Reason}<\/code>. The caller holds the CAFS\nobject lock and the handler must use the caller&#39;s transaction. The standard\nbackend retargets the registry entry, retaining its remote URL; the caller must\nattach the logical filename in that same transaction.<\/li><li><code>cafs_retire<\/code>: retire the redundant legacy <code>path<\/code> only once <code>target_path<\/code> is\nhealthy. Schedule normal retained deletion for redundant remote objects.\nReturn <code>ok<\/code> or <code>{error, Reason}<\/code>;\nqueued legacy uploads must finish first. Use the caller&#39;s transaction.<\/li><li><code>delete_status<\/code>: request delayed deletion and return <code>absent<\/code> only when neither\nremote entries nor queued uploads remain. Return <code>pending<\/code> while work remains,\nor <code>{error, Reason}<\/code> when unavailable or read-only. Exact paths and prefixes are\nsupported. Repeated requests must preserve retention deadlines.<\/li><li><code>purge_previews<\/code>: delete a bounded batch of queued resizer uploads and mark remote\ngenerated previews for deletion, restricted to <code>preview\/<\/code> and the <code>older_than<\/code>\ncutoff. Never delete <code>archive\/preview\/<\/code> images or medium originals. Return\n<code>pending<\/code> while more batches may remain, <code>ok<\/code> once remote deletion is queued,\nor <code>{error, Reason}<\/code>. Physical deletion follows backend retention. CAFS derivatives\nare handled separately by their reference-aware collector.<\/li><li><code>cancel_delete<\/code>: <code>true<\/code> when an undispatched deletion is cancelled or the path\nis absent; <code>false<\/code> once deletion may be in flight. Locking and cancellation must\nparticipate in the caller&#39;s transaction. An error is not confirmation of cancellation.<\/li><\/ul>\n<p>Return <code>undefined<\/code> for unhandled requests. A missing handler does not confirm\ncancellation or remote absence. Backends persist the internal <code>site.filestore_used<\/code>\nmarker before accepting files. Core cleanup uses <code>z_file_request:delete_remote\/2<\/code>\nto distinguish local-only sites from an unavailable previously used backend.<\/p>\n<p>Use a direct module observer for transaction-sensitive operations; passing work\nto a separate process must not lose the caller&#39;s transaction or its locks.\nSee <a href=\"\/id\/doc_module_mod_filestore\" class=\"doc-reference doc-reference-module\"><code>module#mod_filestore<\/code><\/a> and <a href=\"\/id\/doc_model_model_cafs\" class=\"doc-reference doc-reference-model\"><code>model#cafs<\/code><\/a> for storage and retention behavior.<\/p>","slug":"observe_filestore-2","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"956fe278045ac0b9414f0efa1e9d97762dafd1c3695f340fa12be8410b9d344d","is_featured":false,"notification":{"arity":2,"callbacks":[{"arity":2,"name":"observe_filestore","signature":"observe_filestore\/2","variation":"observe"}],"name":"filestore","record":{"action":"upload","filename":"undefined","local_path":"undefined","mime":"undefined","path":"undefined"}},"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":1122,"is_a":["meta","category"],"name":"notification","title":"Notifications","uri":"https:\/\/test.zotonic.com\/id\/1122"},"doc_source_path":"apps\/zotonic_core\/src\/behaviours\/zotonic_observer.erl","publication_start":"2026-11-05T18:42:37Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_core\/src\/behaviours\/zotonic_observer.erl","pivot_location_lng":null,"doc_source_kind":"notification","name":"doc_notification_filestore","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-11-05T18:42:37Z","uri":null,"doc_status":"current","is_dependent":false,"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":"becf161621dcc4de964bbf083a88aff6d3ab21ae\n","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-11-05T18:42:37Z","title_slug":"observe_filestore-2"},"uri":"https:\/\/zotonic.com\/id\/3138","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3138"}},"status":"ok"}