jely2002/youtube-dl-gui · error · Error

Playlist group is missing entry metadata.

Error message

Playlist group ${groupId} is missing entry metadata.

What it means

expandPlaylistGroup resolves a playlist group, its leader item, and their entries metadata before expanding the playlist into individual items. If any of group, leader?.entries, or group.entries is missing, it throws 'Playlist group ${groupId} is missing entry metadata.' Playlists cannot be expanded without the entry list carried by the leader.

Solutions

  1. Confirm the groupId refers to a playlist group whose leader item has an entries array before calling expand.
  2. Check groupStore.findGroupLeader(groupId) and leader.entries in the caller and surface a user-facing 'not a playlist' message instead of throwing.
  3. Ensure the backend payload that creates playlist groups always includes entries on both the group and the leader.
  4. Catch the error where playlist selection is applied and show a toast asking the user to re-add the playlist.

Example fix

// before
await expandPlaylistGroup(groupId, selection);
// after
const leader = groupStore.findGroupLeader(groupId);
if (!leader?.entries) {
  console.warn(`Group ${groupId} is not an expandable playlist`);
  return;
}
await expandPlaylistGroup(groupId, selection);
Defensive patterns

Strategy: validation

Validate before calling

const group = groupStore.findGroupById(groupId);
const leader = groupStore.findGroupLeader(groupId);
if (!group || !leader?.entries || !group.entries) {
  throw new Error(`Group ${groupId} is not an expandable playlist`);
}

Type guard

const isExpandablePlaylist = (groupId: string): boolean => {
  const g = groupStore.findGroupById(groupId);
  const l = groupStore.findGroupLeader(groupId);
  return !!g && !!l?.entries && !!g.entries;
};

Try / catch

try {
  await expandPlaylistGroup(groupId, selection);
} catch (e) {
  if (e instanceof Error && e.message.includes('missing entry metadata')) {
    toast.error('This item is not a valid playlist.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling expandPlaylistGroup (directly or via processMediaAddPayload) for a groupId that is not a playlist group, whose leader item was deleted or replaced, or whose entries metadata was never populated by the backend.

Common situations: Passing a non-playlist (single media) group ID to the playlist expansion path; the leader item was removed by the de-duplication logic in processMediaAddPayload before expansion; backend produced a playlist group without entries metadata.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of jely2002/youtube-dl-gui@c402ee39c0 (2026-09-12). Data as JSON: /api/errors/e892743207a7f2c4. Report an issue: GitHub.

Appendix: source

Thrown at src/stores/media/media.ts:156

    if (group.processed === total) {
      finalizePlaylistGroup(group);
    } else if (group.total === 1 && !hasPlaylistLeader) {
      void notifyGroup(NotificationKind.VideoReady, group);
      resolvePendingReadyGroup(group.id, [group.id]);
    }

    const next = total > 1 && isFirst
      ? MediaState.fetchingList
      : MediaState.configure;
    stateStore.setState(item.id, next);
  }

  async function expandPlaylistGroup(groupId: string, selection: PlaylistSelection) {
    const group = groupStore.findGroupById(groupId);
    const leader = groupStore.findGroupLeader(groupId);
    const entries = group?.entries;
    if (!group || !leader?.entries || !entries) {
      throw new Error(`Playlist group ${groupId} is missing entry metadata.`);
    }

    const selectedEntries = applyPlaylistSelectionToEntries(entries, selection);
    if (selectedEntries.length === 0) {
      throw new Error('No playlist entries match the selected range.');
    }
    const spec = buildPlaylistItemsSpec(selection);
    const previousOverrides = optionsStore.getOverrides(groupId);
    const nextOverrides = cloneDownloadOverrides(previousOverrides) ?? {};

    if (spec) {
      nextOverrides.inputFilters = {
        ...(nextOverrides.inputFilters ?? {}),
        playlistItems: spec,
      };
    } else if (nextOverrides.inputFilters) {
      delete nextOverrides.inputFilters.playlistItems;
      if (Object.keys(nextOverrides.inputFilters).length === 0) {

View on GitHub (pinned to c402ee39c0)