DEV Community

Zhengxin
Zhengxin

Posted on

Turning YouTube Videos Into Obsidian Notes — and What Shipping the Plugin Taught Me

Every so often I'd watch a 90-minute conference talk, close the tab, and realise the only trace it left in my vault was a URL in a list called "watch later (again)".

So I built an Obsidian plugin that turns a video into an actual note. It's been in the community directory since September and is on 0.3.0 now. This is what it does, and — more usefully — the handful of decisions and review rules I'd want to know before building another one.

What lands in the vault

One command, one link, and you get a note:

A video note in Obsidian: embedded player, TL;DR callout and key insights

  • a TL;DR callout and key insights
  • an embedded player at the top — timestamps seek it in place, without leaving the note
  • chapters as timestamped bullets
  • optional summary (several templates), quiz, and full transcript in a collapsed callout
  • frontmatter with title, source, channel, language and tags, shaped for Dataview

Key insights and chapters with timestamp links

Plus a right-sidebar chat panel scoped to the note you're reading, and a command that turns the quiz into Spaced Repetition cards.

Now the parts that were actually decisions.

The plugin holds no model keys

An Obsidian plugin runs on the user's machine. You can't ship an API key in it, and asking every user to paste their own is both friction and a support queue you will personally staff forever.

So the plugin generates nothing. It sends a video link and an account token to a server, and writes down what comes back. That's the whole client.

The honest trade-off: this means the plugin needs an account, which is a real cost to the user and something I put in the README under a Before you install heading rather than burying. A plugin that silently requires a signup is a plugin that gets one-starred.

The connect handshake, and the one line that makes it safe

Settings → Connect opens the browser at /connect/obsidian?state=…&vault=…. You sign in, the server mints a token, and the page bounces you back via obsidian://svt-connect?token=…&state=….

The state is generated by the plugin and checked when the redirect comes home. Without that check, any web page could deep-link a token into your vault — the obsidian:// scheme is open to anyone. It's four lines of code and it's the difference between a handshake and a hole.

Server side the token is stored as a sha256 hash. Client side it lands in .obsidian/plugins/<id>/data.json, which is worth saying out loud in your docs: if the user syncs their vault, that file goes with it.

One note format, two producers

The website also has an Export → Obsidian button. That's two code paths producing "the same" Markdown, which in my experience drift apart within about a week.

So the Markdown is assembled in exactly one place — a server endpoint — and the plugin writes whatever it receives verbatim. The website button and the plugin produce identical notes because they are, literally, the same function call.

Re-running a command must not eat the user's writing

People add their own notes under the generated stuff. Refreshing a note has to be non-destructive or nobody will ever press it twice.

The generated region is fenced:

%% svt:start %%
…everything the server produced…
%% svt:end %%

## My notes
whatever you wrote, untouched
Enter fullscreen mode Exit fullscreen mode

Refresh replaces only what's between the markers. Frontmatter is the subtler half: the server returns an ownedKeys list alongside the note, so the plugin overwrites the keys it owns and leaves every key you added alone. Writes go through vault.process rather than read-then-write, which is also what the community review expects.

requestUrl doesn't stream

Obsidian's requestUrl sidesteps CORS, which you want, because your plugin isn't an origin anyone will whitelist. What it doesn't do is stream.

My summary endpoint is server-sent events. So the plugin waits for the whole response and parses the event stream after the fact. You lose the progressive typewriter effect; you keep mobile support and never fight a preflight. Worth knowing before you architect around a stream that won't arrive.

Flashcards, and only appending

0.3.0 turns the quiz into a card note: multi-line format, the right answer and its explanation on the back, and a timestamp link to the moment the question came from.

Run it again after new questions appear and only the missing cards are appended. Existing cards, their <!--SR:…--> review schedules, and any cards you wrote yourself are never touched. Rewriting a review schedule is the fastest way to lose a user who has been reviewing for six months.

A card note ready for the Spaced Repetition plugin

What the community directory review actually checks

Submitting is no longer a PR against obsidian-releases. You log in at community.obsidian.md, link GitHub, and an automated reviewer runs. Mine came back with a list. The useful items:

  • No inline styles. Anything you'd reach for as el.style.foo = bar belongs in styles.css. My auto-growing textarea became the CSS field-sizing: content instead of a resize handler, which is a better implementation anyway.
  • Settings must use the declarative 1.13 API (getSettingDefinitions() with groups, getControlValue / setControlValue). That forced minAppVersion up to 1.13.0, because the no-unsupported-api rule rejects 1.13 APIs while you claim to support older versions. Pick a lane; you can't straddle.
  • Attest your build. actions/attest-build-provenance in the release workflow, with id-token and attestations write permissions. gh attestation verify main.js --owner <you> exiting 0 with no output is what success looks like.
  • The tag must match the manifest version, and npm version writes tags with a v prefix that Obsidian doesn't want. git tag -d v0.2.2 && git tag 0.2.2.

One false alarm worth pre-empting: right after I re-cut a release, the checker insisted "No release matches your manifest version." It had scanned during the draft window and cached that. Re-scan, it passes. Don't go rewriting your workflow like I nearly did.

Three Obsidian CSS facts that cost me an evening

  • .view-content ships with padding: 12px 12px 32px. If your custom view's layout looks mysteriously inset, that's why. Zero it with a same-specificity selector: .workspace-leaf-content[data-type="…"] .view-content.your-class.
  • On desktop the status bar is position: fixed, 27px tall, and floats over the bottom of the right sidebar. Leave it room or your input box is permanently half-covered.
  • button:not(.clickable-icon) picks up Obsidian's background and shadow. For a text link, use an <a>.

Links

If you're building something that writes into other people's vaults, the markers-and-ownedKeys pattern is the piece I'd steal. Everything else was replaceable; that one is what makes a destructive command safe to press twice.

Top comments (0)