Media Hooks
Accent CMS provides two hooks for plugins to interact with the media pipeline. These hooks allow plugins to augment media metadata during content scanning and transform images after processing.
on_media_discover
The on_media_discover hook fires once for each non-markdown file found during content scanning. Plugins can return metadata key-value pairs that are attached to the media asset and made available in templates.
Use cases:
- EXIF extraction – Read camera metadata from JPEG/TIFF files
- Blur hash generation – Compute compact placeholders for lazy-loading
- Color palette extraction – Identify dominant colors for design integration
- Alt text generation – Use AI services to generate image descriptions
- File validation – Log warnings for oversized or incorrectly formatted files
The hook receives the file path and MIME type as input. The returned metadata map is merged into the MediaAsset and becomes available in templates:
{% for img in page.media | filter_images %}
<img src="{{ img.url }}"
{% if img.metadata.blurhash %}data-blurhash="{{ img.metadata.blurhash }}"{% endif %}
{% if img.metadata.exif %}title="{{ img.metadata.exif.camera }}"{% endif %}>
{% endfor %}
Multiple plugins can contribute metadata. If two plugins return the same key, the later plugin’s value wins.
Example Plugin (Rust)
The pattern follows the same structure as the Writing a Plugin (Rust) section. Deserialize the JSON input, build a metadata map, and return it:
#[plugin_fn] pub fn on_media_discover(input: String) -> FnResult<String> { let input: MediaDiscoverInput = serde_json::from_str(&input)?; let mut metadata = HashMap::new(); if input.mime_type == "image/jpeg" { metadata.insert("format_note".into(), serde_json::json!("JPEG detected")); } Ok(serde_json::to_string(&MediaDiscoverOutput { metadata, error: None })?) }
Set requires_fs = true in plugin.toml if the plugin needs to read file headers directly.
on_media_process
The on_media_process hook fires after core image processing (resizing, format conversion) and before the result is cached. Plugins receive the processed image bytes (base64-encoded) and can modify them, change the content type, or attach metadata.
Use cases:
- Watermarking – Overlay a logo or copyright text on processed images
- Format conversion – Convert to advanced formats (AVIF, JXL) not built into the core
- Quality optimization – Apply perceptual quality tuning beyond what the core processor offers
- Metadata injection – Add processing metadata (dimensions, file size) for analytics
Image bytes are base64-encoded for the JSON protocol. Plugins can return null for image_data to preserve the original bytes while still attaching metadata:
{ "image_data": null, "content_type": null, "metadata": { "optimized": true, "savings_pct": 23 }, "error": null }
Multiple plugins are chained sequentially. Each plugin receives the output of the previous one.
Example Plugin (Rust)
Return null for image_data to preserve the original bytes while attaching metadata:
#[plugin_fn] pub fn on_media_process(input: String) -> FnResult<String> { let input: MediaProcessInput = serde_json::from_str(&input)?; let mut metadata = HashMap::new(); metadata.insert("processed_size".into(), serde_json::json!(input.image_data.len())); Ok(serde_json::to_string(&MediaProcessOutput { image_data: None, content_type: None, metadata, error: None, })?) }
Media Hook Lifecycle
The two media hooks fire at different points in the content and media pipelines:
Content Scanning Media Request
================ =============
Scan directory Receive request
| |
Find media file Resolve file path
| |
Create MediaAsset Check cache (hit? -> serve)
| |
[on_media_discover] Core processing (resize, convert)
| |
Merge metadata into asset [on_media_process]
| |
Build content index Cache result
| |
Available in templates Serve response
Media hooks are independent: a plugin can register for one or both. The discover hook enriches template data while the process hook transforms the served bytes.
The JSON input and output shapes for both hooks are documented in the Communication Protocol chapter.