--- title: "Guide: storage" description: "smabar plugin reference: Guide: storage" canonical: "https://smabar.com/docs/plugin-ref-guide-storage/" lang: en --- # Guide: storage > smabar plugin reference: Guide: storage Canonical HTML version: https://smabar.com/docs/plugin-ref-guide-storage/ · Language: en · Publisher: smabar (https://smabar.com) --- [Build my plugin](https://smabar.com/build-my-plugin.md) · [All references](https://smabar.com/docs/plugin-ref-index.md) · [Install the ZIP](https://smabar.com/docs/install-plugin-zip.md) Source: smabar 1.5.0, revision `a019f2563965edc37a5e8d84742ea1ff2fa6f824`. [Export metadata and checksums](https://smabar.com/plugin-reference/export.json). This is an offline reference. MCP calls quoted below describe the connected workflow; use the linked documents and create local files instead. Live state, installed plugins, available providers and the user's active theme are unknown. Never claim a live check. Read with [styling](https://smabar.com/docs/plugin-ref-styling.md) and [examples](https://smabar.com/docs/plugin-ref-examples.md). ## where ```json "app.data_dir is the ONLY place you write. It is ~/.smabar/data//, it exists before your first handler runs, writing there never restarts you, and it is deleted when the user removes the plugin. Never write next to your code (app.plugin_dir) — that restarts the plugin on every save — and never into ~/.cache, /tmp or a path of your own invention, because nothing cleans those up." ``` ## sqlite ```json "sqlite3 is in the standard library, so a real database costs you no dependency: con = sqlite3.connect(app.data_dir / \"state.sqlite3\"). Handlers share ONE lock, so a single connection reused across handlers is fine — but a worker thread must open its OWN connection. Create your tables with CREATE TABLE IF NOT EXISTS on every start; there is no migration system." ``` ## atomicWrites ```json "Write to \".tmp\" and then os.replace(tmp, final). A plugin killed mid-write otherwise leaves a truncated file, and the next start renders garbage or crashes on it. os.replace is atomic on the same filesystem." ``` ## staleWhileRefresh ```json "The pattern that keeps the bar looking right: on_ready reads the cache and renders it IMMEDIATELY, then a thread fetches fresh data and writes it back. On failure keep the cached values on screen, log once with app.log(\"warn\", …) and try again on the next tick. Never blank the tile because an update failed — a stale value with a quiet marker beats an empty tile." ``` ## assets ```json "Images, audio and video you downloaded or generated go into app.data_dir and are shown with sb-asset: on img/audio/video/source, resolved against your own data directory. Prefer that over a data: URI: a data URI is re-sent on every render, an sb-asset: URL is a few bytes. WebVTT is the exception: read the local text, build data:text/vtt;charset=utf-8, + urllib.parse.quote(text, safe=\"\"), then html.escape the complete URI for its HTML attribute; the asset transport cannot provide the required MIME type. Set crossorigin=\"anonymous\" on audio/video for any cross-origin track, including data:. A bundled media file in app.plugin_dir is code: copy it once into app.data_dir before rendering. The sb-asset helper refuses absolute paths and .., so that URL form cannot reach another plugin's files; plugin markup itself is not a file-access security boundary because the plugin already runs as the user." ``` ## network ```json "There is no HTTP call through the core. Use stdlib urllib.request with an explicit timeout=, or declare your own dependency in the PEP 723 header (e.g. dependencies = [\"httpx\"]) — uv installs it. Because handlers share one lock, run every request in a thread and only publish the result back into your state; a blocking fetch freezes every timer and click of your plugin." ``` ## latestMedia ```json "Use @app.every with a polling interval chosen from provider freshness and rate limits, starting at most ONE worker at a time. Fetch metadata through the provider's documented API/feed with an explicit timeout and a bounded, validated response. On ready, display the stale cache from app.data_dir immediately; the worker writes fresh data with os.replace and keeps the last good result on failure. Escape external titles and URLs. Read current official documentation for authentication, quotas, embed parameters and caching; undocumented endpoints are best-effort fallbacks. Render a thumbnail in tile, separate hover HTML, and a titled HTTPS iframe plus ordinary external link in the pinned flyout. Keep the stable media id: an unchanged id must keep identical media HTML so app.render does not restart playback. smabar supplies its registered app identity through the private wrapper; provider-specific URL parameters remain plugin code. Account, DRM, codec, region or item-level embedding policy can still require the external link." ``` ## inspect ```json "plugin_data lists and reads that directory over MCP, so you can check what your plugin actually wrote. Binary files (a SQLite database among them) come back as metadata only — query those through the plugin's commands. For structured reads and writes register commands: plugin_commands and plugin_call avoid writing the database directly and confirm the committed result." ```