lose-memory77 downloadsCustomize Obsidian background with a local image folder: only the currently used image is loaded into memory, supports folders outside the vault, fixed-interval auto rotation and manual random switch. Does not modify theme variables.
A community plugin for Obsidian that puts your own images behind the workspace.
English · 中文
The background model — opacity, blur, size modes, blend modes and the folder-based random rotation — follows vscode-background-cover, minus its particle effects, desktop pet and online gallery. The Obsidian-side layering approach was informed by obsidian-dynamic-theme-background.
The point of this plugin: your background folder can hold thousands of images (5,000+, tens of GB). Only the one image currently on screen is ever read into memory. The folder itself is scanned for file names only, so library size does not affect performance.


The plugin follows Obsidian's own language setting: Chinese UI on Chinese installs, English everywhere else. The screenshots above show a Chinese install.
Local folder as the image source
d:\wallpapers)~ (home directory) and ${ENV} / $ENV environment variablesMemory-friendly by design
No-repeat shuffle rotation
VS Code-style appearance controls
auto (multiply on light themes, lighten on dark themes, switching instantly with the theme), multiply, lightenRotation
Zero conflict with themes
bgc-). It does not modify any theme CSS variable (--background-*, --text-*, …) and does not override theme stylesOnce the plugin has been reviewed and published, search for Background Cover in Settings → Community plugins → Browse.
Download main.js, manifest.json and styles.css from the latest release.
Put them in a folder named background-cover inside your vault:
<Vault>/.obsidian/plugins/background-cover/
├── main.js
├── manifest.json
└── styles.css
Reload Obsidian and enable Background Cover in Settings → Community plugins.
The folder name must match the
idinmanifest.json(background-cover).Desktop only. The plugin sets
isDesktopOnly: truebecause it reads image files through Node'sfs, which does not exist in the mobile app.
Open Settings → Background Cover.
| Setting | Description |
|---|---|
| Enable background | Master switch. Turning it off only hides the background layer; no theme setting is changed. |
| Background folder | Absolute path to an image folder. Supports ~ and environment variables, and may point outside your vault. The folder is scanned as soon as you enter a path. |
| Image count | Shows how many images were found. Rescan re-reads the folder and reshuffles the playback list. |
| Background opacity | Opacity of the background layer, 0 – 0.8 (default 0.2). |
| Background blur | Blur radius in pixels, 0 – 100 (default 0). |
| Size mode | cover (fill, keeps aspect ratio) / contain (stretch to fill, distorts) / center (scale to fit, centered) / repeat (tile) / natural-size positions. |
| Blend mode | auto (follows the theme) / multiply / lighten. |
| Fade transition | Crossfade smoothly when the image changes (on by default). |
| Make theme background transparent | Makes the theme's opaque background colors transparent so the wallpaper shows its true colors. The only feature that overrides theme colors, off by default — see below. |
| Title bar divider | A 1 px line between the title bar and the workspace (requires the setting above). |
| Sidebar divider | A 1 px line between the sidebars and the workspace (requires the setting above). |
| Divider color | Color of both lines, default neutral gray #808080. |
| Divider opacity | Opacity of both lines, 0 – 1 (default 0.45). |
| Change image on startup | When on, a random image is picked on every launch. When off (default), the image from your last session is restored. |
| Enable auto rotation | Change the background at a fixed interval, off by default. |
| Rotation interval (seconds) | Interval between automatic changes, minimum 5 seconds. |
| Change background now | Immediately take the next image from the folder. |
color-mix and backdrop-filter support, theme variables, overlay DOM structure and computed styles) to the clipboard. If you hit a problem like "the divider does not show up" or "one area still looks different", run this first and attach the output to your issue.The diagnostics report is always in English so that it can be pasted into an issue as-is.
A 🖼 button in the status bar changes the image with one click.
"Random" is not an independent draw each time — it is a shuffled playback list:
If your folder genuinely contains only 2–3 images, "no repeats within a round" will feel like cycling between them — that is a property of the library, not the plugin.
This section describes exactly what the plugin accesses and why. It is required disclosure, not marketing.
d:\wallpapers, not inside the vault. The plugin reads that folder's file names, then reads a single image file when it is displayed. It never writes to that folder, never deletes anything, and never scans folders you did not configure.data.json inside your vault, like any other plugin setting.By default the plugin only adds a background. It does not touch any theme variable — the single exception is the optional Make theme background transparent, which is off by default.
The background is an independent position: fixed full-screen layer composited over the workspace content with mix-blend-mode (the same default effect as vscode-background-cover). Because it is an overlay, the theme's own opaque background colors do not hide the image, so there is no need to rewrite variables such as --background-primary. That is the fundamental difference between this plugin and background plugins that rewrite theme variables.
One trade-off you should know about: the layer uses z-index: 20, deliberately in the middle — above the workspace and the window background bars (Obsidian's --layer-cover 5 / --layer-sidedock 10 / --layer-status-bar 15) and below floating panels (--layer-modal 50 / --layer-notice 60 / --layer-menu 65 / --layer-tooltip 70). Menus, the command palette, notices and hover previews are therefore never tinted by the wallpaper and always look exactly like the theme. If you want the wallpaper to cover those panels too (not recommended — the blend mode will tint them), raise the z-index of .bgc-layer in styles.css.
One exception is worth calling out: with frameless window mode (is-hidden-frameless, common on Windows and Linux), Obsidian itself puts the title bar at --layer-popover (30), above the background layer. The result is that the top strip never shows the wallpaper, only the base color, as if the title bar had an extra filter. When Make theme background transparent is on, the plugin pushes .titlebar down to --layer-status-bar (15) in that mode so it falls below the background layer — it is just a transparent drag area, and the window buttons remain above the sidebar.
Requires Obsidian 1.13.0 or later (dynamic styling uses the official
setCssPropsAPI).
Some themes paint the workspace, sidebars and title bar with opaque background colors (Royal Velvet, for example, uses a palette of --layer-0 … --layer-4 across the whole UI). Those colors take part in the mix-blend-mode computation and pull the wallpaper toward the theme palette: the image looks gray or purple, and its dark areas get lifted. It looks like the theme is applying a filter to your background.
Measured conclusion: this is usually not an actual filter or mask layer in the theme (there is no
filter,backdrop-filteror full-screen overlay to be found in Royal Velvet). It is simply the normal result of a blend mode meeting an opaque base color.
Turn on Settings → Background Cover → Theme integration → Make theme background transparent to make those surfaces transparent so the wallpaper shows its true colors.
.workspace (custom properties inherit downward), while Obsidian's modals live under body, siblings of .app-container and outside .workspace, so they are not affected. Do not move these variables back to body or .theme-*: once --background-primary becomes transparent globally, the settings window, command palette and menus all go transparent with it..view-header, the strip showing the current file name) is handled too. Core sets its background through --file-header-background / --file-header-background-focused, which are also resolved at the root and derive from --background-primary; what is more, its "active leaf + focused window" rule uses four classes ((0,4,0)) and beats an ordinary element-level override, so the plugin blanks the variable instead. A useful diagnostic trick: if an area only looks different while focus is in the note body and returns to normal the moment you click a sidebar or open a separate settings window, you are looking at a focus/active-state rule with higher specificity.--background-modifier-border (which --divider-color defaults to) to transparent, leaving the UI with no dividers at all. With this option on, the plugin draws a 1 px line between the top (tab bar / title bar) and the workspace, and colors the sidebars' .workspace-leaf-resize-handle so the sidebars get their divider back as well. Both lines have their own switches, and the color and opacity are configurable. Only that handle is colored; no other divider in the workspace is touched.lighten lift the entire image, including its dark areas, into a washed-out gray (measured: dark green foreground became pale gray-green). Pinning it to a blend-neutral color is what keeps the wallpaper true to itself. Because it is scoped to the workspace, modals and menus keep their own opaque backgrounds and are unaffected..workspace and need separate treatment, otherwise the wallpaper shows color banding across them. If you do not want them transparent, delete the corresponding rules in styles.css..bgc-layer (carrying mix-blend-mode and blur, whose properties stay completely constant) plus two image children .bgc-layer-image that crossfade by alternating opacity.mix-blend-mode or filter is promoted to its own compositing layer while an opacity animation runs, and the blend mode degrades to normal — the whole screen flashes brighter and covers the UI at the moment of the switch. vscode-background-cover's A6 fix records this trap; this plugin split "blend mode + blur" and "crossfade" onto different elements accordingly.opacity: 0, and its background-image is kept until it is overwritten. An earlier version cleared the "old layer" with a 400 ms timer, which — when you clicked quickly — also wiped the image that had just become active, making the background disappear entirely. Fixed, and the plugin now uses no timers for this at all.blob: URL (about 50 characters) written into the image layer's --bgc-image variable. Do not switch this to data:...;base64,...: that requires putting the whole image into an inline CSS declaration, and browsers impose a length limit on a single CSS declaration value. An over-long value is silently discarded — no error anywhere — after which background-image: var(--bgc-image, none) resolves to none and the background vanishes. Measured in Chromium: a 2 MB value survives intact, a 4 MB value makes getPropertyValue() return an empty string. A high-resolution wallpaper's base64 is routinely several to tens of MB, so the data URL approach manifests as "most images do nothing when clicked, and occasionally a small one works". A blob URL has no length limit and skips base64's 33% overhead.--bgc-* only ever receives legal tokens (none, cover, blur(4px), …) and never nests var(). A var() fallback only applies while the variable is undefined; once a variable is defined to an invalid token, the whole declaration — mix-blend-mode included — is dropped. That is the usual root cause of "the background is there but the blend mode stopped working". Blend mode and transitions are therefore expressed purely as CSS classes, not variables.auto blend mode is pure CSS. body.theme-light / body.theme-dark select multiply / lighten, so switching themes applies instantly with no JS theme listener.getCurrentPath()) and the settings page reads that real state rather than keeping a second copy. Each switch also carries a request sequence number, so only the last read that was started is applied and out-of-order async reads cannot desynchronise the display.Very large images: if your folder contains a few extremely high-resolution images (a panorama can easily exceed 8000×5000), decoding will be noticeably slower and memory will spike briefly when one comes up. That is the cost of the image itself — the plugin still holds only that one. Change background now skips past it.
npm install # install dependencies
npm run dev # watch mode, compiles into output/
npm run build # production build (tsc type check + esbuild bundle) into output/
npm run lint # ESLint
Build artifacts go to output/, which contains main.js, manifest.json and styles.css — a folder that can be dropped straight into a vault or attached to a GitHub release.
Source layout:
src/
main.ts # plugin lifecycle, commands, status bar, folder scan cache
background.ts # background layer: dual image layers, blob URLs, blend classes
settings.ts # settings interface, defaults and the settings tab
i18n.ts # UI strings (English + Chinese) and language selection
css.ts # size-mode → CSS mapping, colour helpers
folder.ts # path expansion, recursive scan, shuffle playlist
diagnostics.ts # the "Copy diagnostics" report
See CONTRIBUTING.md for the constraints that apply to changes, the UI text rules and the release process.
There is no automated test suite in this repository. Changes are verified locally by loading the built main.js in a headless browser against a harness that recreates Obsidian's DOM; that harness depends on a local wallpaper folder, so it is not shipped here. If you change rendering behaviour — blend modes, z-index, the theme-transparency feature — please say in your pull request how you verified it and on which theme.
npm version <x.y.z>. The version script updates manifest.json and versions.json; .npmrc sets tag-version-prefix="" so the git tag is the bare version with no v prefix..github/workflows/release.yml builds the plugin, attests build provenance, and publishes a release with main.js, manifest.json and styles.css attached. The workflow refuses to publish if the tag does not match the version in manifest.json.version in your manifest, so the tag must match exactly and the release must be published, not a draft.The submission process is self-service at community.obsidian.md — there is no longer a pull request to obsidian-releases.
manifest.json's version, with main.js, manifest.json and (optionally) styles.css attached.manifest.json from the HEAD of the default branch, so make sure it is committed. The id must be unique across all published plugins and must not contain obsidian.See Submit your plugin and the submission requirements for the authoritative details.
The design and implementation of this plugin were informed by the following open-source projects:
src/loaderFragments.ts about mix-blend-mode / filter degrading when an element is promoted during an opacity animation is what this plugin's layer structure is built around.The two background projects are MIT-licensed, so the design ideas and parameter semantics borrowed from them are used within their terms. This project is an independent implementation and contains no copied source text from either of them; the attribution above is given because it is the right thing to do, not because a licence compels it.
The scaffold is a different matter: it is derived from obsidian-sample-plugin, so the copyright and permission notice required by its ISC license is retained in THIRD-PARTY-NOTICES.md.
The code and documentation of this project were produced with AI assistance:
deepseek-flash, via the DeepSeek Harness coding agent) — fixed the "background disappears after rapid switching" and "settings page shows a different name than the actual background" defects; found and fixed the deeper root cause (writing images into inline CSS as data URLs exceeds the browser's per-declaration length limit and is silently discarded — measured between 2 and 4 MB — now using blob URLs); restructured the background layers to separate blend mode/blur from the crossfade; completed the blend-mode and opacity failure handling; rewrote the styles, build output and this documentation; verified each change by loading the real build artifact in a headless browser against a local Obsidian DOM harness; and localised the UI, added the contributor and issue-template files, and prepared the project for the community directory.All AI-generated content was reviewed by a human and passes TypeScript type checking, ESLint and a render verification of the built artifact. If you have concerns about AI-assisted development, the source and commit history are open to inspection.
Released under the MIT License.
Disclaimer: This project is an independent community plugin and is not affiliated with or endorsed by Obsidian.