Aaqil Ilyas38 downloadsBack up your YouTube subscriptions, playlists, and Watch Later list into Markdown notes in your vault.
Back up your YouTube subscriptions, playlists, and Watch Later list into Markdown notes in your Obsidian vault. Because accounts get banned, but vaults don't.
Lifeline is a desktop-only Obsidian plugin that exports:
All output is plain Markdown inside your vault. There are no proprietary formats and no databases. Every entry is a record you can query with Dataview.
Lifeline never downloads videos and has no telemetry.
YouTube accounts get terminated, playlists get emptied, and videos disappear without notice. Your Obsidian vault is local and under your control. Lifeline keeps a durable record of what you watched and organized, as plain text, in your vault.
Lifeline is listed in the Obsidian community catalog: community.obsidian.md/plugins/lifeline.
Use this to test a beta build before it reaches the community catalog.
Aaqil101/obsidian-lifeline.main.js, manifest.json, and styles.css from the latest release.<YourVault>/.obsidian/plugins/lifeline/.Lifeline uses the YouTube Data API v3 with your own OAuth credentials, so your quota and your data stay under your control. Google calls this area the Google Auth Platform. Older guides call it the OAuth consent screen and Credentials pages. It is the same setup under renamed menus.
If authorization later fails with a deleted_client error, Google has deleted your client after six months of inactivity (details). Create a new client and update the stored secret.
data.json stores only the secret names and your preferences.The settings tab has five groups. Chevron rows open sub-pages.
| Group | Rows |
|---|---|
| Google account | Connection status and Authenticate with Google / Re-authenticate. Sub-page Google credentials: OAuth client ID, OAuth client secret, and a Security notes button. |
| Notes | Output folder, Journal folder, Identify Shorts, sub-page Video bullet fields, sub-page Views in notes, Styles folder, Script folder, sub-page Channels.md fields, sub-page Dashboard. |
| Images | Channel avatars and banners, Video thumbnails (each Off, Link, or Download), Blurred thumbnails, and Images folder (shown only while one of the two modes is Download). |
| Playlists | Sub-page Playlists to track, sub-page Get saved playlist IDs, and Watch later playlist. |
| Sync | Sync interval (hours) (default 20), Quota soft limit (default 8000), and Sync now. |
Each sub-page has its own rows, described below.
The API returns the playlists you created. It does not return playlists you saved from other channels, and no endpoint lists them. Lifeline collects those IDs from the YouTube page itself. There are two ways, and both feed the same box under Playlists → Get saved playlist IDs:
youtube.com/feed/playlists, opening DevTools, and pasting the script. Select Select collector script to highlight it, then press Ctrl+C; expand Show the collector script to copy it by hand. The plugin never touches the clipboard itself. The script scrolls the page to the end, skips Watch Later, Liked videos, and radio/mix links, and prints a JSON array of IDs. Paste that array into Paste the copied array. Plain lists and mixed text also parse. The count line shows how many IDs were found. Select Add to tracked playlists to save them.Imported IDs are stored in data.json. They appear under Imported IDs on the same sub-page, and each has a trash button to stop tracking it. Lifeline never handles your Google login, and the collector never reads cookies or storage.
Default layout. Folder names are configurable, as noted in each comment.
95 - Notes/YouTube/ # Output folder
├── Channels.md # one field-list entry per channel
├── Dashboard.md # opt-in home note (Dataview)
├── Watch Later.md # your watch-later substitute playlist
├── Playlists/
│ ├── Favorites.md
│ └── Watched later.md
├── thumbnails/ # Images folder: video thumbnails, one sub-folder per playlist
│ └── Favorites/
├── avatars/ # Images folder: channel avatars (Download mode)
└── banners/ # Images folder: channel banners (Download mode)
98 - Organize/Scripts/ # Script folder
├── lifeline-dashboard.js # draws Dashboard.md
├── lifeline-channels.js # draws the Channels.md view (opt-in)
├── lifeline-playlists.js # draws the playlist-note views (opt-in)
└── lifeline-watchlater.js # draws the Watch Later.md view (opt-in)
98 - Organize/Styles/ # Styles folder
├── lifeline-channels.css # read by the Channels.md view (opt-in)
├── lifeline-playlists.css # read by the playlist-note views (opt-in)
├── lifeline-watchlater.css # read by the Watch Later.md view (opt-in)
└── lifeline-dashboard.css # read by Dashboard.md (opt-in)
.obsidian/plugins/lifeline/
├── data.json # settings and collected playlist IDs (no secrets)
├── state.json # sync state: channels, playlists, videos, faces
└── sync-log.json # the most recent 200 sync runs
The Images folder defaults to the output folder, so its images land in 95 - Notes/YouTube/thumbnails/, avatars/, and banners/. Lifeline writes the scripts and stylesheets only when the features that use them are on.
Each channel is one list item with Dataview inline fields:
- [channel:: [Blender Guru](https://www.youtube.com/channel/UCxyz)] [id:: UCxyz] [subscribed_on:: 2024-05-06] [first_seen:: 2024-05-07] [last_seen:: 2026-10-30] [status:: active] [handle:: @blenderguru] [subscribers:: 4670000] [videos:: 1200] [views:: 109000000] [country:: NL]
subscribed_on, and the date fields below, follow the journal rule.Each video is a single list item with Dataview inline fields:
- [title:: [Video Title](https://www.youtube.com/watch?v=abc123)] [channel:: [Blender Guru](https://www.youtube.com/channel/UCxyz)] [date:: 2026-01-05]
date is the day the video was added to the playlist.url frontmatter field that opens the playlist on YouTube.saved: true in frontmatter means the playlist was saved from another channel. saved: false means you created it. The owner channel decides the label, not the import path, so a playlist collected from the same page can still read saved: false.state.json, so the backup keeps them. The frontmatter reports them as unavailable_count and removed_count. Both are always present, so video_count - unavailable_count - removed_count gives the number of bullets. A video that is both removed and unavailable counts once, under removed.[sensitive:: true]. Turning off Blur age-restricted videos keeps that field on age-restricted bullets but leaves them unblurred.Identify Shorts (under Notes) classifies each playlist and Watch Later video while it syncs and adds two fields to the bullet:
- [title:: [Video Title](https://www.youtube.com/watch?v=abc123)] [channel:: [Blender Guru](https://www.youtube.com/channel/UCxyz)] [date:: 2026-01-05] [duration:: 179] [short:: true]
duration is the video's length in whole seconds, so Dataview can sort by it as a number. The views format it for reading.short is written only for a Short. A regular video carries no short field, and a video the classifier could not place carries neither short nor a guess.There are three choices:
youtube.com, where the page itself says whether it is a Short. This makes one full page request per video under 3 minutes. It costs no API quota, the answer is cached in state.json, and each video is requested at most once (an unclear page is retried on a later sync). At most 50 pages are fetched per sync, a fraction of a second apart, so a long list of Shorts fills in over several runs. Answers are saved as they arrive, so a sync you interrupt keeps the ones it already checked.The first sync that looks a video up raises one notice naming this setting, then never again. Changing the choice needs one sync to add or strip the fields. The fields are written whether or not Views in notes is on: the note carries the data, and the view only draws it.
Video bullet fields (under Notes) adds optional facts to playlist and Watch Later bullets. All three are on by default, so a fresh install records them; turning one off strips its field on the next sync. An existing vault picks up the defaults on upgrade and rewrites its playlist notes once on the next sync:
- [title:: [Video Title](https://www.youtube.com/watch?v=abc123)] [channel:: [Blender Guru](https://www.youtube.com/channel/UCxyz)] [date:: 2026-01-05] [published:: 2024-05-03] [blocked:: true]
published, the video's own upload date (not the date it was added to the playlist). It is plain ISO text and never a journal link, because an upload date is almost always in the past. The views show it beside the channel: next to the channel name on Cinema cards and in Ticket rows, and as a Published column in Ledger.blocked only when the video is blocked effectively everywhere: an allowlist, or a block list covering at least 200 countries. YouTube does not say which country you are in, so a partial block writes nothing rather than a badge that means nothing.live for a running stream and upcoming for a premiere. Neither is classified as a Short, and neither carries a duration: a live video has no length yet.Duration and short are deliberately not part of this checklist. They follow Identify Shorts, and a second switch over the same fields would silently do nothing.
The note views draw blocked as a red Blocked badge and upcoming as an amber Upcoming badge in Cinema, Ticket, and Ledger. The two differ in both colour and label. live is not drawn.
Journal folder (under Notes, required) controls whether a bullet's date becomes a wikilink:
[[YYYY-MM-DD]] only when that day's note exists somewhere under the journal folder, or when the date is today or later.Dated fields are the playlist date and Watch Later date, plus Channels.md subscribed_on. first_seen and last_seen are sync bookkeeping and never link. The check runs at sync time, so the next sync after a daily note appears updates the links.
Dashboard.md is an opt-in home note, off by default. Turn it on under Notes → Dashboard → Dashboard note. It needs the community Dataview plugin. Lifeline checks for Dataview and shows a notice if it's missing.
The note holds frontmatter (type, last_synced, channel_count, playlist_count, video_count) and up to three dv.view() calls. There are no headings, because the hero carries the title. Each call runs lifeline-dashboard.js from the Script folder, which queries your synced notes live and draws floating panels over your theme:
Four toggles under Notes → Dashboard turn each panel on or off: New since last sync card, Needs attention card, Playlists card, and Top channels by subscribers. The limits are Feed entries (default 8, for the new-since-sync list), Attention entries (default 6), Playlist cards (default 6), and Top channels (default 10).
Counts are formatted like YouTube (109M, 4.67K). Hover a number to see the full value.
The note and script refresh after every sync. You can also rewrite them on demand with the Create dashboard note command, Refresh dashboard note, or the refresh icon on the Dashboard sub-page. The script is regenerated each time, so hand edits to it are overwritten. Changing Script folder moves the script and deletes the old copy.
If Dataview is disabled later, Lifeline keeps the frontmatter and replaces the sections with a warning callout. The dashboard makes no API calls of its own.
Views in notes is an opt-in toggle, off by default, on the Notes → Views in notes sub-page. When it's on, a live view draws above the entries in Channels.md, in every playlist note, and in Watch Later.md. Each note type has its own script in the Script folder: lifeline-channels.js, lifeline-playlists.js, and lifeline-watchlater.js. Turning the toggle on requires Dataview. Turning it off removes the view blocks from the notes and deletes the scripts. The bullets stay where they are.
Each note type has its own layout row on the same sub-page: Channels layout, Playlists layout, and Watch Later layout. Each offers Poster, Chips, or Table.
subscribed since date in a tinted pane on the right.Layouts are saved per note type. Poster is the default everywhere. An old Cards or Mixed choice loads as Poster.
Every view has a toolbar:
10 / 76 and expands to Showing 10 of 76 channels on hover.short field. It filters the rows, the search count, and the paging immediately, and writes Shorts in views (under Views in notes) so the dashboard feed and every open video view follow with no sync. All is the default, so nothing is hidden until you choose otherwise. It stays hidden on Channels.md and on a note where Identify Shorts has not classified anything yet.Showing 10 of 412 channels, or Found 3 of 412 channels while searching.Each view paints one page of entries and puts the rest behind a Show more button. The page holds 10 entries by default. Change it with Items per page, and it takes effect on the next render.
The views read the note's own list entries (dv.current().file.lists), so they always match the bullets beneath them. Dates that are journal links stay clickable in the views: the Cinema pill, Ticket stub, Ledger Added column, Split subscribed since date, and Cover rows Tracked dates all open that note.
The stylesheets, one per note type, are regenerated on every sync while the views are on. Each script reads its CSS file when it renders, so a missing file leaves that view unstyled but still working. Hand edits to the CSS are overwritten.
Zero API quota cost. Nothing is fetched when a note opens.
Two opt-in image settings, under Images:
avatar_url and banner_url to each channel bullet, and avatar and banner once a file is downloaded.thumbnail_url to each playlist and Watch Later bullet, and thumbnail once a file is downloaded.Image fields are inline data in the bullet, not ![[...]] embeds. The views draw the picture from them. They are written whether or not Views in notes is on. Both settings default to Off. The channel image fields can each be dropped with the Avatar and Banner checkboxes under Channels.md fields.
Each setting has three modes:
[thumbnail_url:: https://i.ytimg.com/vi/abc123/mqdefault.jpg]. The view loads it from Google's image hosts (yt3.ggpht.com for avatars and banners, i.ytimg.com for thumbnails) every time the view renders. That is one request per render, and it tells those hosts which note you opened.[avatar:: [[Blender Guru_UCxyz_avatar.jpg]]], [banner:: [[Blender Guru_UCxyz_banner.jpg]]], or [thumbnail:: [[Some Channel_abc123_Video Title_abc123.jpg]]]. The view prefers the local file, so nothing is requested when you open a note. Images already on disk are never fetched again. Banners are large, often 100-300 KB each.Where Download saves files:
<Images folder>/avatars/.<Images folder>/banners/.<Images folder>/thumbnails/<playlist name>/.Images folder appears when either image setting is set to Download. Leave it empty to keep the files under your output folder. The folder must be inside the vault.
A first sync with Download fetches images a few at a time, before it writes the notes. If the connection drops partway, Lifeline stops downloading, logs one warning, and still writes the notes. A failed image keeps its remote *_url field as a fallback. It drops to no picture only when there is no URL either, so one bad image affects one bullet.
Video thumbnails can also be blurred per video. Under Images → Blurred thumbnails, paste video links or IDs separated by spaces, commas, or new lines. Each blurred thumbnail is revealed with a click. The dashboard never reveals them. Age-restricted videos blur by default; Blur age-restricted videos turns that off while keeping their [sensitive:: true] field, so then only the videos in the list blur.
Image downloads cost no API quota. Thumbnail URLs are built from the video ID. The avatar and banner URLs come from the channel statistics call described under Quota.
Query commands run in the order written (FROM, then FLATTEN, then WHERE), so flatten file.lists before filtering.
Every video across playlists and Watch Later:
TABLE WITHOUT ID v.title AS Video, v.channel AS Channel, v.date AS Added
FROM "95 - Notes/YouTube"
FLATTEN file.lists AS v
WHERE v.title
SORT v.date DESC
All subscriptions, with status. The WHERE clause uses the ID field, so Channel ID must stay on:
TABLE WITHOUT ID v.channel AS Channel, v.subscribed_on AS "Subscribed on", v.first_seen AS "First seen", v.status AS Status
FROM "95 - Notes/YouTube/Channels.md"
FLATTEN file.lists AS v
WHERE v.id
Biggest channels, by subscribers. This needs the Subscribers and Views fields on:
TABLE WITHOUT ID v.channel AS Channel, v.subscribers AS Subscribers, v.views AS "Total views"
FROM "95 - Notes/YouTube/Channels.md"
FLATTEN file.lists AS v
WHERE v.subscribers
SORT v.subscribers DESC
Watch Later only:
LIST WITHOUT ID v.title
FROM "95 - Notes/YouTube/Watch Later.md"
FLATTEN file.lists AS v
WHERE v.title
Saved playlists only:
TABLE file.frontmatter.url AS Link, file.frontmatter.video_count AS Videos
FROM "95 - Notes/YouTube/Playlists"
WHERE saved
sync-log.json.The YouTube Data API allows 10,000 units per day by default. The quota resets at midnight Pacific time. Lifeline counts units for each successful request, and each list call costs 1 unit.
A sync costs roughly:
Image downloads cost no quota.
Quota soft limit (default 8000) is a warning threshold, not a hard stop. A run over it completes and reports a warning. Set a lower value to be warned earlier.
SecretStorage (Obsidian 1.11.4 or newer). That uses the OS credential store, such as Windows Credential Manager or macOS Keychain. They are never written to data.json, state.json, or any file in your vault. Those files hold only secret names, preferences, and sync state.accounts.google.com and oauth2.googleapis.com.www.googleapis.com/youtube/v3, with the youtube.readonly scope.yt3.ggpht.com and i.ytimg.com, but only when an image setting is on Link or Download.127.0.0.1, which mobile Obsidian doesn't support.Watch Later.md.npm install
npm run dev # esbuild watch mode
npm run build # type-check and production bundle
npm run lint # eslint with the obsidianmd plugin rules
npm run deploy # build, then copy artifacts into <GITHUB_DIR>/Obsidian/Zettelkasten/.obsidian/plugins/lifeline (GITHUB_DIR is your GitHub root)
Source layout:
src/main.ts: plugin lifecycle, commands, ribbon, status bar, and persistence.src/auth.ts: OAuth loopback flow and keychain secrets.src/youtubeApi.ts: quota-aware YouTube Data API client.src/sync.ts: sync orchestration, state diffing, vault writes, and image downloads.src/syncLog.ts: the sync log.src/markdown.ts: note renderers, image fields, journal links, and output paths.src/blurredThumbnails.ts: parsing for the blurred-thumbnail list.src/dashboard.ts and src/dashboardScript.ts: Dashboard.md and lifeline-dashboard.js.src/noteViewsScript.ts: the three note-view scripts and their stylesheets.src/collector.ts, src/playlistIds.ts, src/webviewCompat.ts: saved-playlist collection.src/json.ts: type-safe JSON helpers.src/ui/syncLogModal.ts: the sync-log viewer.src/settings/: the settings tab. index.ts declares the five groups. Each group and sub-page is in its own file (notes.ts, viewsPage.ts, dashboardPage.ts, images.ts, account.ts, playlists.ts, collectPage.ts, syncSection.ts, and others). types.ts holds the settings shape, and ui.ts the shared builders.Sync state persists in state.json, and settings persist in data.json. Both live in the plugin folder, next to sync-log.json. None of them holds secrets.
Progress is tracked in GitHub issues.
For a faster dev loop, point the Hot-Reload community plugin at the plugin folder while npm run dev runs.
Copyright (C) 2026 Aaqil101.
Lifeline is free software licensed under the GNU General Public License v3.0 or later.