# Configuration Reference (config.json) This document provides a comprehensive reference for all configuration options in Youtarr's `config.json` file. These settings can be changed from the Configuration page in the web UI. ## Table of Contents - [Configuration File Location](#configuration-file-location) - [Core Settings](#core-settings) - [Plex Integration](#plex-integration) - [YouTube Data API](#youtube-data-api-optional) - [SponsorBlock Settings](#sponsorblock-settings) - [Kodi, Emby and Jellyfin Compatibility](#kodi-emby-and-jellyfin-compatibility) - [Cookie Config](#cookie-config) - [Notifications](#notifications) - [Download Performance](#download-performance) - [Advanced Settings](#advanced-settings) - [Auto-Removal Settings](#auto-removal-settings) - [API Keys & External Access](#api-keys--external-access) - [yt-dlp Auto-Update](#yt-dlp-auto-update) - [Account & Security](#account--security) - [System Fields](#system-fields) - [Configuration Examples](#configuration-examples) - [Best Practices](#best-practices) - [Troubleshooting](#troubleshooting) ## Configuration File Location The configuration file is stored at `./config/config.json` relative to your Youtarr installation directory. ### Auto-Creation The `config.json` is automatically created on first startup if it doesn't exist, with sensible defaults from `config.example.json`. ### Editing Configuration Configuration can be modified through: 1. **Web UI** (recommended) - Configuration page in the application 2. **Manual editing** - Stop Youtarr, edit the JSON file, restart 3. **Environment variables** - Some values can be overridden (see [ENVIRONMENT_VARIABLES.md](ENVIRONMENT_VARIABLES.md)) ## Core Settings ### Youtube Output Directory (env-only, not a config.json field) - **Set via**: `YOUTUBE_OUTPUT_DIR` environment variable in `.env` (see [ENVIRONMENT_VARIABLES.md](ENVIRONMENT_VARIABLES.md)) - **Type**: `string` - **Default**: `"./downloads"` - **Description**: Directory path on the host where downloaded videos are stored - **Note**: This setting is **not** stored in `config/config.json`. It is displayed read-only in the web UI and can only be changed by editing `.env` and restarting. Legacy installs that had `youtubeOutputDirectory` in `config.json` are automatically migrated to `.env` during first-run `.env` bootstrap by `scripts/_create-env.sh` (i.e. the first time the start script runs with no existing `.env`). ### Enable Automatic Downloads - **Config Key**: `channelAutoDownload` - **Type**: `boolean` - **Default**: `false` - **Description**: Automatically download most recent videos from auto-download enabled channels and tabs - **Note**: When true, the newest videos automatically downloaded on a cron schedule ### Download Frequency - **Config Key**: `channelDownloadFrequency` - **Type**: `string` (cron expression) - **Default**: `"0 * * * *"` (hourly) - **Description**: Cron schedule for automatic channel refreshes and downloads - **Examples**: - `"0 */6 * * *"` - Every 6 hours - `"0 2 * * *"` - Daily at 2 AM - `"0 0 * * 0"` - Weekly on Sunday at midnight - `"*/30 * * * *"` - Every 30 minutes ### Files to Download per Channel - **Config Key**: `channelFilesToDownload` - **Type**: `number` - **Default**: `5` - **Description**: Maximum number of most recent videos to download per channel per scheduled auto download - **Range**: 1-10 - **Note**: Applies to scheduled autodownloads and manually triggered channel downloads ### Channel Videos Hot Load (Infinite Scroll) - **Config Key**: `channelVideosHotLoad` - **Type**: `boolean` - **Default**: `false` - **Description**: When enabled, the channel videos page uses infinite scroll: as you scroll the list, the next page of videos is appended to the accumulated results instead of replacing them. When disabled, the page uses paginated navigation with a fixed page size. - **Note**: Affects the UI only; does not change what gets downloaded. ### Preferred Resolution - **Config Key**: preferredResolution - **Type**: `string` - **Default**: `"1080"` - **Options**: `"4320"`, `"2160"`, `"1440"`, `"1080"`, `"720"`, `"480"`, `"360"`, `"240"`, `"144"` - **Description**: Global setting for preferred download resolution - **Note**: Downloads from YouTube at best available quality up to this limit - **Codec implication**: YouTube only provides H.264 in MP4 up to 1080p. Selecting 1440p or 2160p forces Youtarr to pick a VP9 or AV1 source stream (YouTube does not offer H.264 at those resolutions) and remux it into MP4 via `--merge-output-format mp4`. The remux is lossless (no re-encode), but Plex clients without native VP9/AV1 hardware decode (Apple TV HD, older Apple TV 4K, iOS, older Rokus) will transcode at playback. If direct-play compatibility matters more than resolution, keep this at 1080p or set `videoCodec` to `h264`. ### Preferred Video Codec - **Config Key**: `videoCodec` - **Type**: `string` - **Default**: `"default"` - **Options**: `"default"`, `"h264"`, `"h265"` - **Description**: Preferred video codec for downloads. `"default"` picks the best stream YouTube offers at the requested resolution (typically VP9 or AV1 above 1080p). `"h264"` forces H.264/AVC, which maximizes client compatibility but effectively caps resolution at 1080p because YouTube does not serve H.264 above that height. `"h265"` prefers HEVC but YouTube rarely provides it, so it almost always falls back to H.264 MP4. - **Compatibility**: - `h264`: Best compatibility with all devices - `h265`: Better compression, requires modern devices - `vp9`: YouTube's preferred codec - `av1`: Best compression, limited device support ### Default Subfolder - **Config Key**: `defaultSubfolder` - **Type**: `string` - **Default**: `""` (empty - downloads to root directory) - **Description**: Default download location for untracked channels and channels set to use "Default Subfolder" - **Note**: Subfolders are prefixed with `__` on the filesystem (e.g., setting `Sports` creates `__Sports/`) - **Channel Subfolder Semantics**: - **"Default Subfolder"** (NULL in database): Channel uses this global default setting - **"No Subfolder"** (special value): Channel explicitly downloads to root directory, ignoring the global default - **Specific subfolder**: Channel downloads to that specific subfolder - **Use Cases**: - Organize untracked manual downloads into a specific folder - Set a default location while allowing individual channels to override - Explicitly place specific channels in the root directory using "No Subfolder" ### Flat File Structure Default - **Config Key**: `defaultSkipVideoFolder` - **Type**: `boolean` - **Default**: `false` - **Description**: When `true`, new downloads are saved directly in the channel folder (flat structure) instead of an individual per-video subfolder, for every channel that has not chosen its own File Structure setting. - **Channel Override Semantics** (channel setting `skip_video_folder`, edited via the channel's "Video File Structure" select): - **"Use global setting"** (NULL in database): channel follows this global default - **"Flat (no video subfolders)"** (`true`): channel always uses flat structure - **"Video subfolders"** (`false`): channel always uses per-video subfolders, even when the global default is flat - **Note**: Only affects new downloads; existing files are not moved. The manual download dialog can override the structure for a single download ("Force flat" or "Force individual video subfolders"); its default option ("Use channel/global settings") follows the channel setting and this global default. ### Video Filename Template - **Config Key**: `videoFilenamePrefix` - **Type**: `string` - **Default**: `"%(uploader,channel,uploader_id).80B - %(title).64B"`. The title is capped at 64 bytes because the prefix appears twice in the full path (per-video folder + filename) and Plex on Windows silently skips files whose full path reaches 260 characters. Installs that saved settings under an older default keep their persisted value (`.74B`/`.76B`) until the setting is edited. - **Description**: User-customizable prefix for downloaded video filenames AND per-video directory names. Youtarr always appends ` [VIDEO_ID].EXT` to filenames and ` - VIDEO_ID` to per-video folder names so it can re-find your videos on disk; those suffixes are not configurable. - **Syntax**: Uses [yt-dlp's output template syntax](https://github.com/yt-dlp/yt-dlp#output-template). Common tokens: `%(title)s`, `%(uploader)s`, `%(channel)s`, `%(upload_date>%Y-%m-%d)s`, `%(channel_id)s`, `%(display_id)s`. Use `.NB` to byte-truncate values (e.g. `%(title).64B`) or `.Ns` for character truncation (e.g. `%(title).40s`); recommended to keep paths under Windows' 260-char limit. - **Validation**: Empty values, path separators (`/`, `\`), `..`, ASCII control characters, values longer than 160 characters, malformed yt-dlp percent syntax, and invalid truncation like `%(title).40` are rejected. Escape literal percent signs as `%%`. Trailing whitespace is trimmed on save. - **Scope**: Global setting. Applies only to NEW downloads; existing files are not renamed. - **Examples** (all paired with the locked suffixes): - **Default**: `Preston - ESCAPING 99 Nights in the Forest IN REAL LIFE! [Cbq15X05wyY].mp4` - **Date prefix** (`%(upload_date>%Y-%m-%d)s - %(title).64B`): `2025-10-17 - ESCAPING 99 Nights ... [Cbq15X05wyY].mp4` - **Plex YouTube-Agent** (`%(upload_date>%Y_%m_%d)s %(title).64B`): `2025_10_17 ESCAPING 99 Nights ... [Cbq15X05wyY].mp4` (compatible with [Absolute-Series-Scanner](https://github.com/ZeroQI/Absolute-Series-Scanner) and [YouTube-Agent.bundle](https://github.com/ZeroQI/YouTube-Agent.bundle)) - **Title only** (`%(title).64B`): `ESCAPING 99 Nights ... [Cbq15X05wyY].mp4` - **UI**: A live preview in **Settings -> Core Settings -> File Structure Settings** shows the rendered folder and file names against a sample video, with length warnings (yellow > 110 chars, red > 130 chars on the rendered name). ### Enable Subtitles - **Config Key**: `subtitlesEnabled` - **Type**: `boolean` - **Default**: `false` - **Description**: Download subtitles/closed captions with videos ### Subtitle Languages - **Config Key**: `subtitleLanguage` - **Type**: `string` - **Default**: `"en"` - **Description**: Preferred subtitle language(s) when downloading subtitle files for videos (ISO 639-1 code) - **Examples**: `"en"` (English), `"es"` (Spanish), `"fr"` (French) - **Note**: Only displayed when subtitles are enabled ### Dark Mode - **Config Key**: `darkModeEnabled` - **Type**: `boolean` - **Default**: `false` - **Description**: Enable dark mode in web UI ## Plex Integration ### Plex API Key - **Config Key**: `plexApiKey` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Plex authentication token (X-Plex-Token) - **Note**: Can be obtained through Plex OAuth in the web UI or manually entered ### Plex YouTube Library ID - **Config Key**: `plexYoutubeLibraryId` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Default Plex library section ID for YouTube videos. Used for all downloads that do not match a per-subfolder mapping (see below). - **Note**: Library refresh is automatically triggered if configured when new videos are downloaded ### Plex Subfolder Library Mappings - **Config Key**: `plexSubfolderLibraryMappings` - **Type**: `Array<{ subfolder: string | null, libraryId: string }>` - **Default**: `[]` (empty — all downloads use the default library above) - **Description**: Maps channel subfolders to specific Plex library IDs, enabling different subfolders to refresh different Plex libraries after a download. - **Usage**: Configured via the web UI under **Plex Media Server Integration → Per-Subfolder Library Mappings** once connected to Plex. - **Format**: Each entry specifies a `subfolder` (the clean name without the `__` filesystem prefix, or `null` for the root/no-subfolder case) and the target `libraryId`. - **Example**: ```json "plexSubfolderLibraryMappings": [ { "subfolder": "kids", "libraryId": "2" }, { "subfolder": "music", "libraryId": "3" }, { "subfolder": null, "libraryId": "1" } ] ``` - **Fallback**: Any subfolder not listed here will fall back to `plexYoutubeLibraryId`. ### Plex IP - **Config Key**: `plexIP` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Plex server IP address or hostname - **Examples**: `"192.168.1.100"`, `"host.docker.internal"` ### Plex Port - **Config Key**: `plexPort` - **Type**: `string` - **Default**: `"32400"` - **Description**: Plex server port number ### Use HTTPS for Plex - **Config Key**: `plexViaHttps` - **Type**: `boolean` - **Default**: `false` - **Description**: Use HTTPS for Plex connections - **Note**: Enable for remote Plex servers or when SSL is configured ### Plex URL Override - **Config Key**: `plexUrl` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Optional full Plex base URL (e.g., `https://plex.example.com:32400`) - **Usage**: Not configurable via the web UI. Edit `config/config.json` manually or set the `PLEX_URL` environment variable to populate it. - **Note**: When this field is set it takes precedence over the `plexIP`, `plexPort`, and `plexViaHttps` values shown in the UI. ### Plex Playlist Token (advanced) - **Config Key**: `plexPlaylistToken` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Optional override for the token used on playlist-scoped Plex API calls. - **Values**: - `""` / unset: fall back to `plexApiKey`. This is the standard claimed-server case; playlists are visible to the authenticated user. - `"UNCLAIMED_SERVER"`: send playlist requests with no `X-Plex-Token` header. Use this for unclaimed-server LAN setups where Plex Web also accepts unauthenticated calls. - Any other string: use that exact token (route Youtarr-managed playlists through a specific Plex user account other than the admin). - **Usage**: Surface in the UI as an "Advanced" toggle inside the Plex Settings section. Most users do not need to set this. ## Jellyfin Integration These fields are required only when you want Youtarr to mirror playlists to Jellyfin as native playlists. Channel downloads work without them. ### Enable Jellyfin - **Config Key**: `jellyfinEnabled` - **Type**: `boolean` - **Default**: `false` ### Jellyfin URL - **Config Key**: `jellyfinUrl` - **Type**: `string` - **Default**: `""` - **Description**: Base URL of your Jellyfin server (e.g., `http://192.168.1.100:8096`). ### Jellyfin API Key - **Config Key**: `jellyfinApiKey` - **Type**: `string` - **Default**: `""` - **Description**: Created in Jellyfin under **Dashboard -> API Keys**. Redacted in logs. ### Jellyfin User ID - **Config Key**: `jellyfinUserId` - **Type**: `string` - **Default**: `""` - **Description**: User account that will own Youtarr-managed playlists. In the UI, open the **Jellyfin User** dropdown to load accounts from your server and pick one, or use **Enter ID manually** to paste the ID. ### Jellyfin Video Library IDs - **Config Key**: `jellyfinVideoLibraryIds` - **Type**: `array` - **Default**: `[]` - **Description**: Library IDs that contain your Youtarr videos. Optional and safe to leave blank; Youtarr matches downloaded videos to Jellyfin items across all of your libraries. ## Emby Integration These fields work like the Jellyfin fields above, with `emby*` names. They're required only when you want Youtarr to mirror playlists to Emby as native playlists; channel downloads work without them. See [Media Server Playlists](MEDIA_SERVER_PLAYLISTS.md) for setup details. | Config Key | Type | Default | Description | | :--------- | :--- | :------ | :---------- | | `embyEnabled` | `boolean` | `false` | Turn Emby playlist sync on or off. | | `embyUrl` | `string` | `""` | Base URL of your Emby server (e.g., `http://192.168.1.100:8096`). | | `embyApiKey` | `string` | `""` | Created in Emby under **Settings -> Advanced -> API Keys**. Redacted in logs. | | `embyUserId` | `string` | `""` | User account that will own Youtarr-managed playlists. Open the **Emby User** dropdown in the UI to load accounts from your server and pick one. | | `embyVideoLibraryIds` | `array` | `[]` | Library IDs that contain your Youtarr videos. Optional and safe to leave blank; Youtarr matches videos across all of your libraries. | ## YouTube Data API (Optional) ### YouTube API Key - **Config Key**: `youtubeApiKey` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Optional YouTube Data API v3 key. When set, Youtarr uses the API for faster channel metadata, video metadata, and search fetches. On any failure (invalid key, quota exhausted, API disabled, network error), Youtarr silently falls back to yt-dlp with no user-visible error. - **Notes**: - API keys do not expire. The configuration page shows "last validated" timestamp instead of an expiration. - Default quota is 10,000 units per day per Google Cloud project, resetting at midnight Pacific time. Search calls cost 100 units; metadata and channel/playlist calls cost 1 unit per call. - On a 403 `quotaExceeded` response, Youtarr enters an in-memory cooldown until the next Pacific-midnight reset and uses yt-dlp exclusively during that window. - When a key is configured, channel video listing and tab auto-detection use the API for all three tabs (Videos, Shorts, Streams) via the per-tab auto-generated playlist IDs (`UULF`/`UUSH`/`UULV`). yt-dlp remains the fallback. - Search results filter out live/upcoming broadcasts and Shorts under 60s to match yt-dlp's behavior. The Shorts filter requires a follow-up `videos.list` enrichment call to read each result's duration; if that enrichment fails (e.g., quota burned mid-search), the search returns the un-enriched results and a small number of Shorts may slip through. Live/upcoming filtering still applies in that fallback path. - Set up instructions and a test button are in the Configuration page under Integrations. ## SponsorBlock Settings ### Enable SponsorBlock - **Config Key**: `sponsorblockEnabled` - **Type**: `boolean` - **Default**: `false` - **Description**: Enable SponsorBlock to skip/remove sponsored segments ### SponsorBlock Action - **Config Key**: `sponsorblockAction` - **Type**: `string` - **Default**: `"remove"` - **Options**: `"remove"`, `"mark"` - **Description**: How to handle sponsored segments - `remove`: Cut segments from video file - `mark`: Add chapters to mark segments ### SponsorBlock Categories - **Config Key**: `sponsorblockCategories` - **Type**: `object` - **Default**: ```json { "sponsor": true, "intro": false, "outro": false, "selfpromo": true, "preview": false, "filler": false, "interaction": false, "music_offtopic": false } ``` - **Description**: Which segment types to skip/remove ### SponsorBlock API URL - **Config Key**: `sponsorblockApiUrl` - **Type**: `string` - **Default**: `""` (uses default SponsorBlock API) - **Description**: Custom SponsorBlock API server URL (optional) - **Example**: `"https://sponsor.ajay.app"` ## Kodi, Emby and Jellyfin Compatibility ### Write Channel Posters - **Config Key**: `writeChannelPosters` - **Type**: `boolean` - **Default**: `true` - **Description**: Generate channel poster images for media servers - **Note**: Creates poster.jpg in each channel directory ### Write Video NFO Files - **Config Key**: `writeVideoNfoFiles` - **Type**: `boolean` - **Default**: `true` - **Description**: Generate NFO metadata files for Kodi/Jellyfin/Emby - **Note**: Creates .nfo XML files with video metadata ### Write Video Fanart - **Config Key**: `writeVideoFanart` - **Type**: `boolean` - **Default**: `false` - **Description**: Create fanart image files for video backgrounds in media servers - **Note**: Creates a `-fanart.jpg` file alongside each video with the video thumbnail. Some Plex clients (notably NVIDIA Shield) use this as the background preview image instead of or alongside the poster. When enabled with `writeChannelPosters`, videos will display correctly on all Plex clients with both a poster (from channel thumbnail) and background (from video thumbnail). ## Cookie Config ### Enable Cookies - **Config Key**: `cookiesEnabled` - **Type**: `boolean` - **Default**: `false` - **Description**: Use cookies for YouTube authentication - **Note**: May be required in some cases to get around YouTube bot detection ### Custom Cookies Uploaded - **Config Key**: `customCookiesUploaded` - **Type**: `boolean` - **Default**: `false` - **Description**: Indicates if custom cookies.txt file has been uploaded - **Note**: Managed automatically by the application ## Notifications Youtarr uses [Apprise](https://github.com/caronc/apprise) to send notifications when new videos are downloaded, supporting 100+ notification services. ### Enable Notifications - **Config Key**: `notificationsEnabled` - **Type**: `boolean` - **Default**: `false` - **Description**: Enable notifications when new videos are downloaded ### Apprise URLs - **Config Key**: `appriseUrls` - **Type**: `array` of objects - **Default**: `[]` (empty array) - **Description**: List of notification service configurations Each entry in the array is an object with the following properties: | Property | Type | Description | |----------|------|-------------| | `url` | `string` | Apprise-compatible notification URL | | `name` | `string` | Friendly name for this notification (e.g., "Discord - Gaming Server") | | `richFormatting` | `boolean` | Enable rich formatting (embeds, styled text) when supported | **Example Configuration:** ```json { "appriseUrls": [ { "url": "discord://webhook_id/webhook_token", "name": "Discord Server", "richFormatting": true }, { "url": "tgram://bot_token/chat_id", "name": "Telegram Group", "richFormatting": true }, { "url": "ntfy://my-topic", "name": "Ntfy Mobile", "richFormatting": false } ] } ``` ### Rich Formatting For supported services, Youtarr sends beautifully formatted notifications with embeds, styled text, video cards, and timestamps. Services without rich formatting support receive plain text notifications. | Service | URL Format | Rich Formatting | |---------|------------|-----------------| | Discord | `discord://webhook_id/webhook_token` | ✅ Embeds with colors, thumbnails | | Telegram | `tgram://bot_token/chat_id` | ✅ HTML formatting | | Slack | `slack://token_a/token_b/token_c` | ✅ Block Kit formatting | | Email | `mailto://user:pass@gmail.com` | ✅ HTML email with styling | | Pushover | `pover://user_key@app_token` | ❌ Plain text | | Ntfy | `ntfy://topic` | ❌ Plain text | | Other services | Various | ❌ Plain text | Toggle "Rich formatting" off on any webhook to send plain text instead. ### Supported Services Apprise supports 100+ notification services. See the [Apprise Notification Services Wiki](https://github.com/caronc/apprise/wiki#notification-services) for a complete list and URL formats. Common services include: - **Discord**: `discord://webhook_id/webhook_token` - **Telegram**: `tgram://bot_token/chat_id` - **Slack**: `slack://token_a/token_b/token_c` - **Pushover**: `pover://user_key@app_token` - **Ntfy**: `ntfy://topic` or `ntfys://your-server/topic` - **Email**: `mailto://user:pass@gmail.com` - **Matrix**: `matrix://user:pass@hostname/#room` - **Gotify**: `gotify://hostname/token` ### Migration from Discord Webhook If you previously used the `discordWebhookUrl` configuration option, Youtarr automatically migrates it to the new `appriseUrls` format on startup: **Before (legacy):** ```json { "discordWebhookUrl": "https://discord.com/api/webhooks/123/abc", "notificationService": "discord" } ``` **After (automatic migration):** ```json { "appriseUrls": [ { "url": "https://discord.com/api/webhooks/123/abc", "name": "Discord Webhook", "richFormatting": true } ] } ``` The old `discordWebhookUrl` and `notificationService` fields are automatically removed after migration. No manual action is required. ## Download Performance ### Download Socket Timeout - **Config Key**: `downloadSocketTimeoutSeconds` - **Type**: `number` - **Default**: `30` - **Description**: Network timeout for download connections (seconds) - **Range**: 10-300 - **Note**: Corresponds to yt-dlp `--socket-timeout` setting. Time to wait before giving up, in seconds. ### Download Throttled Rate - **Config Key**: `downloadThrottledRate` - **Type**: `string` - **Default**: `"100K"` - **Description**: Bandwidth limit for downloads - **Examples**: `"500K"`, `"1M"`, `"10M"`, `""` (unlimited) - **Note**: Corresponds to yt-dlp `--throttled-rate` setting. Minimum download rate in bytes per second below which throttling is assumed and the video data is re-extracted. ### Download Retry Count - **Config Key**: `downloadRetryCount` - **Type**: `number` - **Default**: `2` - **Description**: Number of retry attempts for failed downloads - **Range**: 0-10 - **Note**: Used for yt-dlp `--fragment-retries` and `--retries` settings. ### Auto-Retry Failed Videos - **Config Key**: `downloadAutoRetryCount` - **Type**: `number` - **Default**: `1` - **Description**: Number of times a video that fails with a transient HTTP 403 is automatically re-queued in a fresh download job - **Range**: 0-3 (`0` disables auto-retry) - **Note**: YouTube sometimes rejects an already-issued stream URL mid-download with HTTP 403. yt-dlp's own retries (`downloadRetryCount`) re-request the same rejected URL and cannot recover; only a fresh yt-dlp run with a fresh extraction can. When a video fails with the 403 signature, Youtarr queues an "Auto-retry" job for just that video. Permanent failures (members-only, terminated channels, bot detection) are never auto-retried. ### Enable Stall Detection - **Config Key**: `enableStallDetection` - **Type**: `boolean` - **Default**: `true` - **Description**: Detect and abort stalled downloads - **Note**: Setting to control whether stall detection window and rate threshold are used. ### Stall Detection Window - **Config Key**: `stallDetectionWindowSeconds` - **Type**: `number` - **Default**: `30` - **Description**: Time window for stall detection (seconds) ### Stall Detection Rate Threshold - **Config Key**: `stallDetectionRateThreshold` - **Type**: `string` - **Default**: `"100K"` - **Description**: Minimum download rate before considering stalled ### Sleep Between Requests - **Config Key**: `sleepRequests` - **Type**: `number` - **Default**: `1` - **Description**: Delay between YouTube API requests (seconds) - **Note**: Corresponds to yt-dlp `--sleep-requests` setting. ## Advanced Settings ### Proxy - **Config Key**: `proxy` - **Type**: `string` - **Default**: `""` (empty) - **Description**: HTTP/HTTPS proxy for downloads - **Format**: `"http://proxy:port"` or `"socks5://proxy:port"` - **Note**: The proxy is used by yt-dlp for all YouTube requests (downloads, metadata, thumbnails). Some operations like thumbnail downloads and RSS feed checks first attempt a direct HTTP request with a 15-second timeout before falling back to yt-dlp. SOCKS5 proxy users may notice brief delays (~15 seconds) during these fallbacks when adding or refreshing channels, but the operations will complete successfully via yt-dlp. ### IP Family - **Config Key**: `ytdlpIpFamily` - **Type**: `string` (one of `"ipv4"`, `"ipv6"`, `"auto"`) - **Default**: `"ipv4"` - **Description**: IP family preference applied to every yt-dlp invocation. - `"ipv4"` adds `-4` (force IPv4) — recommended for YouTube reliability and the historical default. - `"ipv6"` adds `-6` (force IPv6). - `"auto"` adds neither flag and lets the OS decide. - **Note**: Force IPv6 or Auto can make YouTube downloads less reliable. Use only if your network requires it. ### Download Rate Limit - **Config Key**: `ytdlpDownloadRateLimit` - **Type**: `string` - **Default**: `""` (empty — no limit) - **Description**: Maximum download rate, passed to yt-dlp as `--limit-rate`. Format: digits with optional decimal and optional `K`/`M`/`G` suffix (e.g. `"5M"`, `"500K"`, `"1.5M"`). Empty disables the limit. ### Custom yt-dlp Arguments - **Config Key**: `ytdlpCustomArgs` - **Type**: `string` - **Default**: `""` (empty) - **Description**: Free-form yt-dlp arguments appended to every invocation. Tokenized shell-style (single/double quotes and backslash-escapes supported). Maximum length: 2000 characters. - **Blocked flags**: For safety, several flags are rejected at save time and silently dropped at command-build time. The full list is in `server/modules/download/customArgsParser.js`; it includes (among others) `--exec`, `--netrc-cmd`, `-o`/`--output`, `-P`/`--paths`, `--print-to-file`, `--external-downloader`/`--downloader`, `--external-downloader-args`/`--downloader-args`, `--cookies`/`--cookies-from-browser`, `--ffmpeg-location`, `--config-location`, `--batch-file`, `--load-info-json`, `--download-archive`, plus the flags that have dedicated config fields (`--proxy`, `-4`/`-6`, `--limit-rate`, `--sleep-requests`). - **Order**: Custom args are appended LAST in the yt-dlp command, after Youtarr's managed flags. Per yt-dlp's last-wins semantics, your flags can override managed ones (e.g. `--retries 5` overrides Youtarr's default `--retries 2`). - **Note**: Power-user feature. Incorrect flags can prevent downloads from working entirely or break Youtarr's behavior in unexpected ways. Use the "Validate Arguments" button in the UI to argparse-check your args against yt-dlp before saving. The validation does not gate save — invalid args can still be saved and will only surface failures at download time. ### Use External Temporary Directory - **Config Key**: `useTmpForDownloads` - **Type**: `boolean` - **Default**: `false` - **Description**: Controls where downloads are staged before moving to final location: - `false` (default): Downloads are staged in a hidden `.youtarr_tmp/` directory within your output folder. Uses fast atomic renames since source and destination are on the same filesystem. The dot-prefix hides in-progress downloads from media servers like Plex and Jellyfin. - `true`: Downloads are staged in the external path specified by `tmpFilePath` (e.g., `/tmp`). Useful when your output directory is on slow network storage and you want to download to fast local storage first. - **Note**: Some managed platforms (e.g., ElfHosted) force this value on. ### External Temporary File Path - **Config Key**: `tmpFilePath` - **Type**: `string` - **Default**: `"/tmp/youtarr-downloads"` - **Description**: External temporary directory for downloads when `useTmpForDownloads` is `true` - **Note**: Only used when `useTmpForDownloads` is enabled. Internal path in Youtarr container. ### NFS Output Directory Considerations If your output directory (`YOUTUBE_OUTPUT_DIR`) is on an NFS mount, be aware of the following: **When `useTmpForDownloads: true`:** Downloads are staged on a different filesystem from the output directory. The move from temp to output is a cross-filesystem copy+delete, which is vulnerable to NFS stale mount errors. If the NFS mount goes stale, downloads succeed to the temp dir but fail during the move phase. Critically, yt-dlp marks the video as "downloaded" in its archive *before* the move, so the video becomes permanently stuck — it won't be retried on the next scheduled run because yt-dlp thinks it already succeeded. **Recommended NFS mount options:** If using NFS, mount with options that prevent stale file handles: ``` server:/export /mnt/nfs-output nfs hard,intr,actimeo=3,timeo=300,retrans=5 0 0 ``` - `hard` — retries NFS operations indefinitely instead of failing immediately - `intr` — allows signals to interrupt hung NFS operations - `actimeo=3` — refreshes NFS attribute cache every 3 seconds (default 60s can cause stale metadata) - `timeo=300,retrans=5` — longer timeouts before declaring failure **Docker native NFS volumes (recommended):** Instead of bind-mounting a host NFS directory, let Docker mount NFS directly. This is more resilient because Docker manages the NFS connection rather than inheriting a potentially-stale host mount: ```yaml services: youtarr: volumes: - youtube-data:/usr/src/app/data # named NFS volume - ./server/images:/app/server/images - ./config:/app/config - ./jobs:/app/jobs volumes: youtube-data: driver: local driver_opts: type: nfs o: addr=YOUR_NFS_SERVER_IP,hard,intr,nfsvers=4,actimeo=3 device: ":/path/to/your/nfs/export" ``` **Simplest workaround:** Set `useTmpForDownloads: false` (the default). Downloads are staged inside the output directory itself, so the move is a same-filesystem rename — atomic and immune to this class of error. Note: if the NFS mount is stale, downloads will still fail, but they will fail *before* yt-dlp marks them as archived — so they'll be automatically retried on the next scheduled run rather than getting permanently stuck. ## Auto-Removal Settings ### Enable Auto-Removal - **Config Key**: `autoRemovalEnabled` - **Type**: `boolean` - **Default**: `false` - **Description**: Enable automatic deletion of old videos ### Free Space Threshold - **Config Key**: `autoRemovalFreeSpaceThreshold` - **Type**: `string` - **Default**: `null` (not set) - **Description**: Minimum free space to maintain - **Examples**: `"100GB"`, `"500GB"`, `"1TB"` - **Note**: Deletes oldest videos when space falls below threshold ### Video Age Threshold - **Config Key**: `autoRemovalVideoAgeThreshold` - **Type**: `string` - **Default**: `null` (not set) - **Description**: Delete videos older than this age - **Examples**: `"30d"` (30 days), `"3m"` (3 months), `"1y"` (1 year) ## API Keys & External Access Settings for API key authentication used by bookmarklets, mobile shortcuts, and automation tools. ### API Key Rate Limit - **Config Key**: `apiKeyRateLimit` - **Type**: `number` - **Default**: `10` - **Description**: Maximum download requests per minute per API key - **Range**: 1-100 - **Note**: Helps prevent abuse from external integrations. Each API key is rate-limited independently. For detailed information on creating and using API keys, see [API Integration Guide](API_INTEGRATION.md). ## yt-dlp Auto-Update Youtarr can optionally check for and install yt-dlp updates on a nightly schedule. The toggle and its status display live next to the manual yt-dlp update button on the Core Settings page. ### Auto-Update Enabled - **Config Key**: `autoUpdateYtdlp` - **Type**: `boolean` - **Default**: `false` - **Description**: When `true`, Youtarr runs `yt-dlp -U` at 4:00 AM (server local time, controlled by the `TZ` env var) every night. - **Behavior**: - The update is skipped while any download job is in progress and is retried the next night. - If the update process itself fails (e.g., permission denied on managed platforms, network error, timeout), the failure is logged and Youtarr continues to run on the previous yt-dlp version. - On success, the in-process yt-dlp version cache is refreshed without requiring a server restart. ### Last Checked Timestamp - **Config Key**: `ytdlpLastChecked` - **Type**: `string | null` (ISO 8601 timestamp) - **Default**: `null` - **Description**: Set automatically every time the nightly job runs (regardless of outcome). Surfaced in the UI as "Last checked: ...". - **Note**: Managed by the application; do not edit by hand. ### Last Updated Timestamp - **Config Key**: `ytdlpLastUpdated` - **Type**: `string | null` (ISO 8601 timestamp) - **Default**: `null` - **Description**: Set automatically when the nightly job successfully installs a new yt-dlp version. Not updated when the check finds yt-dlp is already current. - **Note**: Managed by the application; do not edit by hand. ### Last Run Result - **Config Key**: `ytdlpLastResult` - **Type**: `object | null` - **Default**: `null` - **Shape**: `{ status: 'updated' | 'up-to-date' | 'skipped' | 'error', message?: string, version?: string }` - **Description**: Records the outcome of the most recent nightly run. The UI uses this to render an inline status next to "Last checked". - **Statuses**: - `updated` — a new version was installed; `version` holds the new version string. - `up-to-date` — yt-dlp was already current. - `skipped` — the run was deferred (for example, a download was in progress); `message` describes why. - `error` — `yt-dlp -U` failed; `message` holds a short error description. - **Note**: Managed by the application; do not edit by hand. ## Filesystem Rescan For user-facing documentation on when and how to use the filesystem rescan (moving files, converting formats, supported extensions), see [Rescan Files on Disk](USAGE_GUIDE.md#rescan-files-on-disk). ### Last Rescan Result - **Config Key**: `rescanLastRun` - **Type**: `object | null` - **Default**: `null` - **Shape**: ```json { "startedAt": "2026-05-04T15:13:00.000Z", "completedAt": "2026-05-04T15:14:32.000Z", "trigger": "manual | scheduled | startup", "status": "completed | timed-out | error", "videosUpdated": 12, "videosMarkedMissing": 3, "videosScanned": 8421, "filesFoundOnDisk": 8423, "errorMessage": null } ``` - **Description**: Records the outcome of the most recent filesystem reconciliation pass (the `backfillVideoMetadata` run). Written by the daily cron, the server-startup pass, and the manual "Rescan files on disk" action on the Maintenance settings page. Surfaced read-only on that page so users can see when the last scan ran and what it found or fixed. - **Note**: Managed by the application; do not edit by hand. ## Account & Security ### username - **Type**: `string` - **Default**: Not set (must be configured) - **Description**: Login username for the web interface - **Validation**: 1-32 characters, no leading/trailing spaces - **Note**: Set during initial setup or via AUTH_PRESET_USERNAME environment variable ### passwordHash - **Type**: `string` - **Default**: Not set (must be configured) - **Description**: Bcrypt hash of the login password - **Note**: Never edit directly - use web UI or AUTH_PRESET_PASSWORD environment variable ## System Fields These fields are managed automatically by the application: ### uuid **Type**: `string` **Default**: Auto-generated **Description**: Unique installation identifier **Note**: Currently unused ## Configuration Examples See config/config.example.json ## Best Practices 1. **Backup your config.json** before major changes 2. **Use the Web UI** for configuration when possible 3. **Test cron expressions** at [crontab.guru](https://crontab.guru/) 4. **Monitor disk space** when enabling auto-downloads 5. **Start conservative** with download frequency to avoid rate limiting ## Troubleshooting ### Configuration Not Saving - Check file permissions: `ls -la config/config.json` - Ensure proper ownership matches YOUTARR_UID/GID - Check logs for write permission errors ### Missing Configuration Options in UI - Clear browser cache - Ensure you're running the latest version - Check browser console for JavaScript errors ### Downloads Not Running on Schedule - Verify cron expression syntax - Check timezone setting (TZ environment variable) - Review logs for scheduler errors