Website: https://youmenutube.riccardo.lol/
A macOS menu-bar app that puts YouTube in your menubar:
- Home (default tab): YouTube's main recommendations feed
- Latest videos from your Subscriptions
- Browse your Playlists (including Watch Later and Liked Videos)
- Search YouTube
- Settings for default playlist, autoplay, floating-window behaviour, hide-Shorts (per feed), launch-at-login, and version / update-check info
- Click any video to play it in a chrome-less, 16:9 floating mini player
- In-app update check against the latest GitHub release
Built with SwiftUI MenuBarExtra, targeting macOS 15 Sequoia or later.
Powered by YouTubeKit. It talks to
YouTube's own internal "InnerTube" API directly. No Google Cloud project,
no API key, no OAuth client to set up. Sign into youtube.com in your
usual browser (passkeys, password managers, all of it) and YouMenuTube
imports the session. Supports Safari, Chrome, Edge, Arc, Brave, Vivaldi,
Opera, Helium, Firefox and Zen.
YouMenuTube is unofficial and not affiliated with, endorsed by, or sponsored by YouTube, Google, or Alphabet. All trademarks belong to their respective owners.
The app authenticates by importing
youtube.comcookies out of your browser's on-disk cookie store (same trick yt-dlp uses), then talks to YouTube's internal InnerTube API via YouTubeKit. This is the same surface YouTube's own website uses, but it is not a public API and using it is arguably outside YouTube's Terms of Service. The API can (and occasionally does) change or break without notice.Use at your own risk. Recommendations:
- Treat this as a personal-use project, not a production tool.
- Strongly consider using a secondary Google account, not your primary one.
- Don't expect notifications, video uploads, or anything beyond read-mostly playback / browsing.
- The maintainers accept no liability for account suspensions, data loss, or anything else (see
LICENSE).
- Grab the latest DMG from the website or directly from the latest release on GitHub (that link permanently redirects to the newest published version, so you can bookmark it).
- Open the DMG and drag
YouMenuTube.appinto/Applications. - First launch: because the build is ad-hoc signed (no paid Apple
Developer ID), Gatekeeper will refuse it on a normal double-click. Either:
- Right-click the app → Open → Open again in the prompt, or
- System Settings → Privacy & Security → "YouMenuTube was blocked …" → Open Anyway.
- Subsequent launches work normally.
The app checks /releases/latest once per launch (in Settings → About) and
shows an "Update available" link if a newer version is published.
YouMenuTube/
├── bootstrap.sh # One-shot setup: prereqs + project generation
├── project.yml # XcodeGen spec → generates YouMenuTube.xcodeproj
├── .swift-format # swift-format config (line length 120, 4-space)
├── .githooks/pre-commit # swift-format lint on staged Swift files
├── .github/
│ ├── dependabot.yml # Monthly grouped updates for SPM + Actions
│ └── workflows/
│ ├── ci.yml # Lint + build + test (PR-label gated)
│ ├── release.yml # Builds the DMG on v* tag push
│ ├── auto-tag.yml # Patch-bumps tag on Sources-bearing pushes
│ └── bump-release.yml # Manual minor / major bump
├── Sources/
│ ├── Info.plist
│ ├── YouMenuTube.entitlements
│ ├── App/
│ │ ├── YouMenuTubeApp.swift
│ │ └── RootView.swift
│ ├── Services/
│ │ ├── YouTubeService.swift # one client for everything
│ │ ├── DemoData.swift # -demo-mode fixtures for screenshots
│ │ ├── PlayerController.swift
│ │ ├── RefreshTrigger.swift # shared "user pressed refresh" signal
│ │ ├── UpdateChecker.swift # polls /releases/latest
│ │ ├── LoginItem.swift # SMAppService launch-at-login toggle
│ │ ├── DockPresence.swift # Toggles NSApp activation policy when auxiliary windows open
│ │ ├── Keychain.swift
│ │ └── BrowserCookieImport/ # Reads youtube.com cookies from the user's browser
│ │ ├── Browser.swift
│ │ ├── BrowserDetector.swift
│ │ ├── BrowserCookieImporter.swift
│ │ ├── SQLiteReader.swift
│ │ ├── FirefoxCookies.swift
│ │ ├── ChromiumCookies.swift
│ │ └── SafariBinaryCookies.swift
│ ├── Models/
│ │ └── YouTubeModels.swift # VideoEntry, PlaylistEntry
│ ├── Utilities/
│ │ └── Constants.swift # WindowID, UserAgent, BuiltInPlaylist
│ └── Views/
│ ├── VideoRow.swift # VideoRow, VideoList, ErrorInline, ThumbnailView
│ ├── VideoFeedList.swift # Shared load/error/empty/list wrapper
│ ├── HomeFeedView.swift
│ ├── SubscriptionsFeedView.swift
│ ├── PlaylistsView.swift # API + WL/LL synthetic rows
│ ├── SearchView.swift
│ ├── SettingsView.swift
│ ├── PlayerWindow.swift
│ └── ImportSessionWindow.swift
├── Tests/
│ └── SmokeTests.swift # Swift Testing target
├── LICENSE # Apache 2.0
├── NOTICE # Third-party attribution
├── SECURITY.md
├── CONTRIBUTING.md
└── README.md
brew install xcodegen # (if missing, bootstrap.sh will offer to install it for you)
./bootstrap.shThis generates YouMenuTube.xcodeproj, resolves YouTubeKit via SPM, and
opens Xcode. In Xcode:
- Pick a signing team (target → Signing & Capabilities → Team); a free personal team works.
- Build & Run (⌘R).
- There's no Dock icon (by design:
LSUIElement). Look for the▶️ icon in the menu bar. - Click the icon → Sign in → pick the browser where you're already signed in to youtube.com. First import may ask for a one-time permission (see "How sign-in works" below). Session cookies land in the macOS Keychain and the window closes.
bootstrap.sh flags: --no-open, --clean.
The project ships a second scheme, YouMenuTube Demo, which launches the
app with -demo-mode. In that mode YouTubeService swaps every network
call for the hardcoded fixtures in
Sources/Services/DemoData.swift:
popular real video IDs, so thumbnails render normally and clicking a row
plays the real video in the embedded player. No sign-in, no keychain, no
InnerTube traffic. Edit DemoData.swift to change what the screenshots
show.
You can also launch from the command line:
open ./path/to/YouMenuTube.app --args -demo-mode
# or
YMT_DEMO_MODE=1 ./path/to/YouMenuTube.app/Contents/MacOS/YouMenuTube| Feature | Endpoint (YouTubeKit / InnerTube) |
|---|---|
| Home recommendations | HomeScreenResponse |
| Subscriptions feed | AccountSubscriptionsFeedResponse |
| Your playlists | AccountPlaylistsResponse |
| Playlist contents | PlaylistInfosResponse (browseId VL…) |
| Watch Later | PlaylistInfosResponse (browseId VLWL) |
| Liked Videos | PlaylistInfosResponse (browseId VLLL) |
| Search | SearchResponse |
YouMenuTube doesn't host a sign-in UI of its own. Instead it imports
youtube.com cookies directly out of your browser's cookie store, so the
sign-in itself happens wherever your passkeys / password manager already
work: in your normal browser. Three formats covered:
| Browser family | Storage | What macOS will ask |
|---|---|---|
| Safari | Binary cookies inside the Safari container | One-time Full Disk Access grant (System Settings → Privacy & Security → Full Disk Access → add YouMenuTube). Without it the import fails cleanly. |
| Chrome / Edge / Arc / Brave / Vivaldi / Opera / Helium (Chromium family) | SQLite + AES-128-CBC with a key in the login Keychain | A standard "YouMenuTube wants to use confidential information stored in 'Chrome Safe Storage' …" Keychain prompt. Click Always Allow once per browser. (Helium's Keychain entry is Helium Storage Key rather than the usual … Safe Storage, but the flow is identical.) |
| Firefox / Zen (Firefox family) | Plain SQLite, unencrypted | No prompt. |
The importer filters strictly to *.youtube.com-scoped rows; mixing in
.google.com or accounts.google.com cookies makes InnerTube respond
with loggedOut=true. The resulting blob is persisted in the macOS
Keychain and handed to YouTubeKit via YouTubeModel.cookies.
If your browser isn't signed in to youtube.com yet, the import window
shows a "Sign in to YouTube in [Browser]" button that opens your browser
to https://www.youtube.com/signin for you. Come back to YouMenuTube
once signed in and click Import.
The Now Playing window wraps youtube.com/embed/<id> in a tiny HTML page
loaded with baseURL = https://youmenutube.local/. The fake-but-real-looking
parent origin is what makes YouTube's IFrame player initialize. Loading
the embed URL top-level returns error 153, and baseURL = youtube.com
(same-origin parent) returns 152-4.
The window itself uses .windowStyle(.plain) for a fully chrome-less look,
locked to a 16:9 aspect ratio via NSWindow.aspectRatio. Because plain
(borderless) windows can't normally become key or be resized, the underlying
NSWindow's styleMask is patched to add .resizable, which restores both
edge-resize handles and Cmd+W. A 28pt strip at the top of the player fades
in on hover, hosting a small ✕ close button and a performDrag(with:)-backed
draggable region (since the WKWebView itself swallows mouse events). By
default the window floats above other apps; toggle this in Settings.
| Tool | What | How to run |
|---|---|---|
swift-format |
Format & lint Swift sources | xcrun swift-format format -i -r --configuration .swift-format Sources Tests (write) / ... lint --strict ... (check) |
| Swift Testing | Unit tests under Tests/ |
xcodebuild ... test (or ⌘U in Xcode) |
| Pre-commit hook | Runs swift-format lint on staged files | Auto-installed by bootstrap.sh (git config core.hooksPath .githooks); see .githooks/pre-commit |
| GitHub Actions CI | Lint + build + test on macos-15. Opt-in only: add the run-ci label to a PR (or use the "Run workflow" button) so macOS minutes aren't burned on every push |
.github/workflows/ci.yml |
| Dependabot | Monthly grouped updates for SPM packages and GitHub Actions | .github/dependabot.yml |
Configuration lives in .swift-format (line length 120, 4-space indent).
See CONTRIBUTING.md for PR conventions and detail.
Releases are fully automated. The pipeline:
- Auto-tag (
.github/workflows/auto-tag.yml): every push tomainthat touchesSources/,Tests/,project.yml, orbootstrap.shtriggers a tag. The bump level is derived from the conventional-commit subjects since the previous tag: any!:/BREAKING CHANGE:commit → major, otherwise anyfeat(...):commit → minor, otherwise → patch. Docs / dependabot / hook / format-config pushes don't trigger it. - Release (
.github/workflows/release.yml): fires onv*tag pushes, builds the.app(Release config), ad-hoc signs it, packages it as a.dmgwithcreate-dmg, and publishes a GitHub Release. - Bump & Release (
.github/workflows/bump-release.yml): manual entry point (Actions tab → Run workflow) to force a specific bump or cut a release out-of-band.
Versioning: CFBundleShortVersionString is derived from git describe
(post-build script in project.yml): 0.1.0 on a tag, 0.1.0+N past it.
CFBundleVersion is git rev-list --count HEAD (monotonic, kept for
macOS update bookkeeping even though it isn't shown in UI). GitCommit
holds the short SHA (with a -dirty suffix when the working tree has
uncommitted changes). Settings → About displays marketing version and
commit, e.g. 0.1.0 · 19d5410.
- Import says "Safari's cookies live inside a protected container":
macOS requires Full Disk Access to read
~/Library/Containers/com.apple.Safari/…. System Settings → Privacy & Security → Full Disk Access → add YouMenuTube (the Import window has a shortcut button for this). Re-run Import. - Import says "Couldn't read [Browser]'s cookie-encryption key from the Keychain": you clicked Deny on the macOS Keychain prompt. Open Keychain Access, search for "Safe Storage" for that browser, open the entry → Access Control tab → remove YouMenuTube from the deny list (or delete the ACL), then re-run Import and click Always Allow.
- Import says "[Browser] isn't signed in to YouTube": the browser you picked doesn't have a valid YouTube session. Click the "Sign in to YouTube in [Browser]" button, complete sign-in there, then re-run Import.
- Subscriptions / playlists empty after signing in: enable verbose
logging to see what the import produced:
log stream --predicate 'subsystem == "app.youmenutube"' --level debug. If session markers (SAPISID, SID, LOGIN_INFO, __Secure-3PSIDTS) are all present but InnerTube still saysloggedOut=true, YouTubeKit may be out of date; check for a newer release. - Embed player shows error 150 / 101: the uploader has disabled embedding for that specific video; it can only be played on youtube.com.
- Embed player shows error 153 / 152-4: should not happen with the
current wrapper; if it does, check
Sources/Views/PlayerWindow.swiftfor changes tobaseURLor the iframeoriginquery parameter.
YouMenuTube runs entirely on your Mac. There is no telemetry, no analytics, no crash reporting back-channel, no remote config. The app reaches the network only for:
- YouTube's own InnerTube endpoints for feeds, playlists, and search
api.github.com/repos/gek0z/YouMenuTube/releases/latestonce per launch to render the update-available link in Settings → About
Sign-in itself happens in your browser, not in YouMenuTube; the app
doesn't make network requests to Google / YouTube for authentication. It
reads youtube.com cookies out of your browser's on-disk cookie store
(filtered to *.youtube.com only), stores them in the macOS Keychain
under service com.youmenutube.app, and uses them as the Cookie header
on its own InnerTube requests. Sign out at any time to wipe them
(Settings → Account → Sign out).
YouMenuTube is licensed under the Apache License 2.0. See NOTICE for third-party attribution.