DIYgod/RSSHub · error · ConfigNotFoundError

YouTube RSS is disabled due to the lack of <a href="https://

Error message

YouTube RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#route-specific-configurations">relevant config</a>

What it means

ConfigNotFoundError thrown by the YouTube subscriptions handler. Unlike /c/ and /live, this route needs the full OAuth stack — config.youtube.key AND clientId AND clientSecret AND refreshToken — because it reads the authenticated user's own subscription feed via the YouTube Data API on their behalf. The guard checks all four and fails if any is missing.

Source

Thrown at lib/routes/youtube/subscriptions.ts:50

                description: '',
            },
        ],
    },
    radar: [
        {
            source: ['www.youtube.com/feed/subscriptions', 'www.youtube.com/feed/channels'],
            target: '/subscriptions',
        },
    ],
    name: 'Subscriptions',
    maintainers: ['TonyRL'],
    handler,
    url: 'www.youtube.com/feed/subscriptions',
};

async function handler(ctx) {
    if (!config.youtube || !config.youtube.key || !config.youtube.clientId || !config.youtube.clientSecret || !config.youtube.refreshToken) {
        throw new ConfigNotFoundError('YouTube RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#route-specific-configurations">relevant config</a>');
    }
    const embed = !ctx.req.param('embed');

    const channelIds = (await getSubscriptions('snippet', cache)).data.items.map((item) => item.snippet.resourceId.channelId);

    const playlistIds = await pMap(channelIds, async (channelId) => (await getChannelWithId(channelId, 'contentDetails', cache)).data.items?.[0].contentDetails.relatedPlaylists.uploads, { concurrency: 30 });

    let items = await pMap(playlistIds.filter(Boolean), async (playlistId) => (await getPlaylistItems(playlistId, 'snippet', cache))?.data.items, { concurrency: 30 });

    items = items.flat();

    items = items
        .filter((i) => i && !i.error && i.snippet.title !== 'Private video' && i.snippet.title !== 'Deleted video')
        .map((item) => {
            const snippet = item.snippet;
            const videoId = snippet.resourceId.videoId;
            const img = getThumbnail(snippet.thumbnails);
            return {

View on GitHub (pinned to bed535e087)

Solutions

  1. Create OAuth credentials (Desktop app type) in Google Cloud Console and set YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET.
  2. Use the Google OAuth Playground (redirect URI https://developers.google.com/oauthplayground) with the youtube.readonly scope to obtain a YOUTUBE_REFRESH_TOKEN.
  3. Set YOUTUBE_KEY as well (the route needs both API key and OAuth).
  4. Restart RSSHub after setting all four env vars.
Defensive patterns

Strategy: validation

Validate before calling

import { config } from '@/config';
function requireYouTubeOAuth() {
    const c = config.youtube;
    const missing = [];
    if (!c?.key) missing.push('YOUTUBE_KEY');
    if (!c?.clientId) missing.push('YOUTUBE_CLIENT_ID');
    if (!c?.clientSecret) missing.push('YOUTUBE_CLIENT_SECRET');
    if (!c?.refreshToken) missing.push('YOUTUBE_REFRESH_TOKEN');
    if (missing.length) {
        throw new ConfigNotFoundError(`YouTube subscriptions needs OAuth config. Missing: ${missing.join(', ')}. Use redirect URI https://developers.google.com/oauthplayground with scope youtube.readonly.`);
    }
}

Prevention

When it happens

Trigger: Any request to /youtube/subscriptions when at least one of YOUTUBE_KEY, YOUTUBE_CLIENT_ID, YOUTUBE_CLIENT_SECRET, YOUTUBE_REFRESH_TOKEN is unset. The guard short-circuits before getSubscriptions is called.

Common situations: Operator set only YOUTUBE_KEY and assumed subscriptions would work; OAuth credentials not created in Google Cloud; refresh token never generated via the OAuth playground; env vars named inconsistently.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/f4a4cbaffe5e3e6e. Report an issue: GitHub.