My Own Icons

by arnaudfeld
5
4
3
2
1
Score: 50/100

Description

An Obsidian plugin for using your own custom icons and popular icon libraries like Devicon, Simple Icons and Lucide.

Reviews

No reviews yet.

Stats

0
stars
21
downloads
0
forks
1
days
0
days
1
days
0
total PRs
0
open PRs
0
closed PRs
0
merged PRs
0
total issues
0
open issues
0
closed issues
38
commits

Latest Version

2 days ago

Changelog

English

Version 0.0.13 of My Own Icons. M.O.I. is the short name of the project.

This release fixes a bug that broke every icon

Versions 0.0.11 and 0.0.12 show no icons at all. Do not use them.

The cause was mine. To satisfy a lint rule I replaced document.createElement("span") with document.createSpan(). That looks equivalent and is not. Obsidian patches Node.prototype.createEl and there it always sets parent to the receiver, so the new element is appended. Called on document, it throws HierarchyRequestError, because a document is allowed exactly one child. The global createSpan() without a receiver appends only when info.parent is set and otherwise returns a detached element, which is what document.createElement did.

Seven places were affected: the editor widget, the explorer rows, the badge in tabs and titles, both color probes, the file input in the export and the icon preview in the settings tab. All of them now use the global form. A test reads every plugin file and fails if a create helper is ever called with a document as the receiver again, so this cannot come back silently.

The settings header appeared twice

My own doing as well. The definition set name and the render callback then drew the same name a second time. Name and slogan now both come from Obsidian, through name and desc. The three CSS classes that existed only for that custom header are gone.

Also in this version

  • The review flagged an assertion in setControlValue that did not change the type. It is replaced by a type guard over the five switch names. That was a real fix, not just a lint one: the old k in DEFAULT_SETTINGS also matched the two path fields, so a boolean could have landed in a string field.
  • The review also flagged vault enumeration. listSvgNames walked the whole vault with vault.getFiles() just to filter one folder. It now walks that folder through getFolderByPath, so the plugin no longer touches file paths outside its own icon folder.

Bundle 198 KB, 78 tests green, both TypeScript checks and the formatter clean.

Install

Download manifest.json, main.js and styles.css from this release and copy them into <Vault>/.obsidian/plugins/moi-icons/, then enable the plugin under Settings, Community plugins. If you came from 0.0.11 or 0.0.12, replace all three files.


Deutsch

Version 0.0.13 von My Own Icons. M.O.I. ist die Kurzform des Projekts.

Diese Fassung behebt einen Fehler, der jedes Icon kaputt gemacht hat

In 0.0.11 und 0.0.12 wird kein einziges Icon angezeigt. Beide Fassungen nicht benutzen.

Die Ursache war meine. Um eine Lint-Regel zu erfüllen, habe ich document.createElement("span") durch document.createSpan() ersetzt. Das sieht gleichwertig aus und ist es nicht. Obsidian patcht Node.prototype.createEl und setzt dort immer parent auf den Empfänger, das Ergebnis wird also angehängt. Auf document aufgerufen wirft das HierarchyRequestError, weil ein Dokument genau ein Kind haben darf. Der globale createSpan() ohne Empfänger hängt nur, wenn info.parent gesetzt ist, und liefert sonst ein losgelöstes Element, genau wie vorher document.createElement.

Betroffen waren sieben Stellen: das Editor-Widget, die Explorer-Zeilen, der Badge in Tabs und Titeln, beide Farb-Sonden, das Datei-Feld im Export und die Icon-Vorschau im Settings-Tab. Alle nutzen jetzt die globale Form. Ein Test liest jede Plugin-Datei und schlägt fehl, sobald doch wieder ein create-Helfer mit document als Empfänger aufgerufen wird.

Der Settings-Kopf stand zweimal da

Ebenfalls mein Fehler. Die Definition setzte name, und der render-Callback malte denselben Namen noch einmal. Name und Slogan kommen jetzt beide von Obsidian, über name und desc. Die drei CSS-Klassen, die es nur für diesen eigenen Kopf gab, sind weg.

Ebenfalls in dieser Fassung

  • Der Review hat eine Assertion in setControlValue bemängelt, die den Typ nicht ändert. Ersetzt durch einen Type Guard über die fünf Schalternamen. Das war eine echte Korrektur: das alte k in DEFAULT_SETTINGS traf auch die beiden Pfadfelder, ein Boolean hätte in einem String-Feld landen können.
  • Der Review hat außerdem die Vault-Enumeration gemeldet. listSvgNames lief mit vault.getFiles() über den ganzen Vault, um einen Ordner herauszufiltern. Jetzt läuft es über getFolderByPath nur durch diesen Ordner, das Plugin fasst keine Dateipfade außerhalb seines Icon-Ordners mehr an.

Bundle 198 KB, 78 Tests grün, beide TypeScript-Prüfungen und der Formatter sauber.

Installation

manifest.json, main.js und styles.css aus diesem Release herunterladen und nach <Vault>/.obsidian/plugins/moi-icons/ kopieren, dann unter Einstellungen, Community-Plugins aktivieren. Wer von 0.0.11 oder 0.0.12 kommt, ersetzt alle drei Dateien.

README file from

Github

M.O.I. – My Own Icons

M.O.I. – My Own Icons: colored icons in the file explorer, the {{icon:…}} shortcode and the frontmatter fields

Deutsch: README.de.md

Why this exists

I wanted to use my own SVG files in Obsidian. Simple as that sounds, I could not find a plugin that did it the way I wanted. The ones I tried knew a lot about the interface, but they pulled their icons from somewhere else, and I was left exporting files by hand to get the icons I had drawn myself into a note.

So I started writing my own. The first version did three things: it read a folder of my SVGs, it turned a shortcode into an icon, and it let me set an icon and a color on any file or folder with a right click. Everything after that came out of using it.

What makes it different: the icon in a note is just a shortcode.

{{icon:my-own-icon}}

Put that anywhere in a note and the plugin looks for _assets/icons/my-own-icon.svg. Draw the SVG yourself, drop it in that folder, and it shows up. No import step, no format to learn, no list of pre-approved icons. If the file is not there, the text stays text and the console tells you which name it looked for.

On top of that it loads Devicon, Simple Icons and the selfh.st homelab set on demand, and it can use the icons Obsidian already ships.

How it was built

I wrote it with opencode, mostly with Claude models, sometimes others. It started as a rough version in a day and then went through a lot of review rounds. Most of the work was not adding features, it was finding the mistakes.

Three rounds of review so far, and each found real bugs rather than cosmetics. A file could be overwritten by two mappings at once when two tabs fired simultaneously. Icons inside code blocks turned into pictures, so an example of the shortcode syntax could not be written down. Untrusted settings from data.json were taken at face value, so a wrong type in a path field crashed the icon lookup. The bundle was 289 KB because nobody had turned on minification; it is 198 KB now.

The test suite grew with it, from nothing to 77 tests. npm test has to pass before a commit lands, and both the plugin tree and the test scripts are type checked.

That is the honest state of it: the code is mine and I understand it, but I did not write every line by hand, and the review rounds did more for the quality than the first drafts did.

What it does

  • {{icon:name}} anywhere in a note, with optional size, color and a dark theme variant
  • Icons in the file explorer, set with a right click, with color and size per file or folder
  • The same icon in the tab bar and in the note title
  • Frontmatter fields icon, iconColor, iconSize and iconDark for a single note
  • One icon per file extension as a fallback
  • Icon picker with search, preview, favorites, and a free hex color
  • Autocomplete for the shortcode and for the frontmatter fields
  • Icon gallery listing everything assigned, plus unused files
  • Load Devicon, Simple Icons and selfh.st on demand, cached on the device
  • Export and import the whole setup as a JSON package
  • Interface in German, English, French and Spanish

No runtime dependencies.

Install

Copy manifest.json, main.js and styles.css into <Vault>/.obsidian/plugins/moi-icons/ and enable the plugin under Settings, Community plugins. Those three files are the whole plugin.

To build it yourself, clone the repository and run:

npm install --legacy-peer-deps
npm run build

The --legacy-peer-deps is required, because obsidian asks for an older @codemirror/state than the installed one. After a code change only main.js changes and has to be copied again. When the version changes, manifest.json changes too. data.json is never copied, it holds your settings.

Shortcode in notes

{{icon:name}}
{{icon:devicon/proxmox}}
{{icon:simple/homeassistant}}
{{icon:lucide:lucide-server}}
{{icon:name|24}}
{{icon:name|1.5em}}
{{icon:name|red}}
{{icon:name|24|blue}}
{{icon:name|dark:simple/docker}}

The default folder is _assets/icons and can be changed in the settings. Without a size, 1em applies; a plain number counts as pixels. Second to fourth part in any order: size, color as a theme name or CSS color, or a dark variant with dark:.

lucide: uses the icons Obsidian already ships, no file needed. Note the doubled prefix, lucide:lucide-server. Obsidian names its icons lucide-server, and the lucide: in front of it belongs to this plugin. Which icons exist depends on your Obsidian version, so look them up in the picker or in the suggestions.

While typing {{icon: the plugin suggests names with a preview, and Devicon search also understands tags like database.

If a file is missing, the text stays as it is, [name] appears with a tooltip, and the console tells you which name it looked for. A shortcode inside a code block is never touched, in the editor as well as in reading view, so you can write the syntax down and show it to someone.

Emoji work in the explorer mapping as emoji:📁, but not as a shortcode, because the icon name may only contain letters, digits, -, _, /, : and ..

Explorer icons via right-click

Right-click a file or folder, then Change icon. In the picker you can set a color and an optional size per entry, for example Homelab to 1.4em. The same works via right-click inside an open note. The command Choose icon for active file opens the same dialog and can be bound to a shortcut under Settings, Hotkeys. If you want the shortcode in the text, use right-click, Insert icon, or the command Insert icon into note, also assignable to a shortcut.

The dialog shows search with a preview over your own SVGs, Devicon, Simple and Lucide. Below that a color row with nine theme colors like in Iconic, plus none and a free hex picker. Remove icon deletes the assignment. A multi-selection gets the same icon at once.

It is stored in _assets/icon-mapping.json in the vault, for example:

{
  "Homelab/proxmox.md": { "icon": "devicon/proxmox" },
  "Homelab": { "icon": "simple/homeassistant", "color": "blue", "iconDark": "simple/homeassistant" }
}

The short form "path": "devicon/docker" without a color stays valid. Renaming and deleting keeps the file up to date, including children for folders. iconDark names a variant for the dark theme; in the shortcode that is {{icon:name|dark:simple/docker}}.

File type icons

In the settings, one icon per extension as a fallback after path and frontmatter, for example md to lucide:lucide-file-text. Starts empty, extension without a dot. Applies to explorer, tabs and titles; folders never fall under it. Stored under __ext__ in the same mapping file; export and gallery know that section.

Tabs, titles and frontmatter

The tab bar and the note title show the same icon from mapping or frontmatter, switchable per area. In frontmatter, icon, iconColor, iconSize and iconDark apply, and frontmatter wins over mapping. When editing the source, the plugin suggests values for icon, iconColor and iconDark.

Colors

Devicon files with exactly one color are recolored when a color is chosen; multi-colored ones and gradients keep their original colors. The same applies to your own SVGs and Simple Icons with exactly one fixed color, for example black paths. Anything without a fixed color follows the chosen color via currentColor.

If your icon is invisible in the dark theme, it has a fixed black color built into the SVG and does not inherit the text color. Pick a color in the plugin and it overwrites the fixed one.

Loading from CDN

In the settings you can enable loading from CDN. If a devicon or simple icon is missing as a file, the plugin fetches it from jsdelivr and caches it on the device, limited to 150 entries and 1.5 MB. Failures are retried after 5 minutes. The name lists come live from devicon.json, slugs.md and the selfh.st index.json, with a built-in fallback plus a version display and a reload button. In the picker all names appear with a cloud symbol, and selecting one loads it on demand. Offline, the cache keeps working. Via Save as file, a CDN icon lands as an SVG in _assets/icons, for example for editing or for Git.

A separate switch covers self-hosted icons from selfh.st as selfhosted:name, around 2,400 homelab brands with tags. In the dark theme the light variant is used automatically when available, and the light variant is on by default; a manually chosen dark variant always wins.

Export and import

The commands Export icons and Import icons, as well as the buttons in the settings, pack the mapping plus used SVGs into an icons-export-YYYY-MM-DD.json in the vault, or read it back. Exporting twice on the same day writes a second file rather than overwriting. Existing files are kept during import; files over 500 KB, as well as more than 10 MB or 500 files per package, are skipped, as are more than 5000 mapping entries.

Sync and devices

The CDN cache lives in data.json and is synced by Obsidian Sync, even though it is meant per device. For shared vaults, keep an eye on size and traffic; if needed, clear the icon cache in the settings.

The mapping file is made for sync, but without merging: if two devices set different icons at the same time, the last writer wins. The plugin does not read conflict copies. If the mapping file is deleted while the plugin is running, the in-memory state survives and is written again on the next set.

Picker extras

The picker shows favorites and recently used at the top, each only when present. The star in each row pins or unpins an icon. Via Choose dark icon you can set a variant for the dark theme, then click an icon in the list. If the chosen color is hard to read on the theme background, the picker warns. Both lists live per device in the plugin data.

The command Open icon gallery lists all assigned icons with path and a remove button, plus unused files in the icon folder. The command Check icons reports mapping entries without a target and counts unused files, without network. The command Reload icons re-reads folders and caches. If Iconic, Iconize or Icon Folder is active at the same time, the plugin shows a notice once per session, since all of them compete for the same DOM spots.

Security

SVGs from your own folder and from the network are cleaned before they reach the page. Scripts, event handlers, external references and embedded styles are removed, and what is left is rendered as an SVG rather than as HTML. If a file is not usable after cleaning, the plugin shows [name] instead of an empty space.

CDN icons are fetched from jsdelivr only, and only when you turn the setting on. Nothing else leaves your machine.

Mobile

All commands run on the phone. To insert on the go, put a command such as Insert icon into note on the mobile toolbar under Settings, Toolbar; plugins cannot add buttons there directly. Right-click means long-press.

Language

The interface follows Obsidian's language setting and is available in German, English, French and Spanish. This covers commands, context menus, the settings tab, the picker, the gallery and all messages. The slogan uses the same language: Local, lightweight, yours. (de Lokal, leicht, deins., fr Local, léger, à vous., es Local, ligero, tuyo.). If Obsidian is set to another language, English is used.

Warnings in the developer console stay German; those are messages for debugging, not interface text. This is the English version; the German one is in README.de.md.

Starter icons

starter-icons/ holds 30 Devicon homelab icons, 10 Simple Icons brands, and Dockhand, Technitium and AdGuard Home as a base. Copy the contents to _assets/icons/ in the vault, then for example {{icon:dockhand}} works. Generated with python3 scripts/fetch-icons.py.

License

MIT, see LICENSE.

Bundled third-party content is listed in LICENSES-THIRD-PARTY.md: Devicon is MIT, Simple Icons is CC0, selfh.st icons are CC-BY-4.0 with attribution to selfh.st. Lucide is not bundled, its icons come from Obsidian itself. Trademarks and logos belong to their respective owners and serve only to identify them in your own documentation.