{"record":{"id":"ad79dee5b406530c","repo":"mermaid-js/mermaid","slug":"invalid-icon-loader-must-have-either-icons-or","errorCode":null,"errorMessage":"Invalid icon loader. Must have either \"icons\" or \"loader\" property.","messagePattern":"Invalid icon loader\\. Must have either \"icons\" or \"loader\" property\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/mermaid/src/rendering-util/icons.ts","lineNumber":43,"sourceCode":"\nconst iconsStore = new Map<string, IconifyJSON>();\nconst loaderStore = new Map<string, AsyncIconLoader['loader']>();\n\nexport const registerIconPacks = (iconLoaders: IconLoader[]) => {\n  for (const iconLoader of iconLoaders) {\n    if (!iconLoader.name) {\n      throw new Error(\n        'Invalid icon loader. Must have a \"name\" property with non-empty string value.'\n      );\n    }\n    log.debug('Registering icon pack:', iconLoader.name);\n    if ('loader' in iconLoader) {\n      loaderStore.set(iconLoader.name, iconLoader.loader);\n    } else if ('icons' in iconLoader) {\n      iconsStore.set(iconLoader.name, iconLoader.icons);\n    } else {\n      log.error('Invalid icon loader:', iconLoader);\n      throw new Error('Invalid icon loader. Must have either \"icons\" or \"loader\" property.');\n    }\n  }\n};\n\nconst getRegisteredIconData = async (iconName: string, fallbackPrefix?: string) => {\n  const data = stringToIcon(iconName, true, fallbackPrefix !== undefined);\n  if (!data) {\n    throw new Error(`Invalid icon name: ${iconName}`);\n  }\n  const prefix = data.prefix || fallbackPrefix;\n  if (!prefix) {\n    throw new Error(`Icon name must contain a prefix: ${iconName}`);\n  }\n  let icons = iconsStore.get(prefix);\n  if (!icons) {\n    const loader = loaderStore.get(prefix);\n    if (!loader) {\n      throw new Error(`Icon set not found: ${data.prefix}`);","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/mermaid-js/mermaid/blob/d93e9c88c01a599c062ee6a3f1462e3558ac6b90/packages/mermaid/src/rendering-util/icons.ts#L25-L61","documentation":"Thrown by registerIconPacks after the name check passes but the loader object has neither a `loader` function (async pack) nor an `icons` object (static pack). The registry stores one or the other keyed by name, so the object must carry exactly one of these properties. The preceding log.error('Invalid icon loader:', iconLoader) prints the offending object.","triggerScenarios":"Passing { name: 'logos' } alone, { name: 'logos', src: '...' } (wrong key like `src`), or { name: 'logos', icon: {...} } (singular `icon` instead of `icons`). Any name-bearing object missing both `loader` and `icons` keys hits the final else branch.","commonSituations":"Mistyping the property (`Icon`/`Loader` capitalized, `iconData`, `data`), or upgrading from an older API where the shape differed. Also from partial spreads: { name: 'x', ...rest } where rest happened to omit both keys.","solutions":["Provide exactly one of `loader: () => Promise<IconifyJSON>` (async/lazy) or `icons: IconifyJSON` (static/eager) on each loader object.","For lazy loading use loader: () => import('@iconify-json/logos').then((m) => m.icons); for eager use import { icons } from '@iconify-json/logos' and pass { name, icons }.","Check the console immediately before this error — log.error prints the full invalid object so you can see which key is missing/misspelled.","Validate against the IconLoader union type so TypeScript flags objects missing the required discriminating property."],"exampleFix":"// before\nmermaid.registerIconPacks([{ name: 'logos' }]);\n// after\nmermaid.registerIconPacks([\n  {\n    name: 'logos',\n    loader: () => import('@iconify-json/logos').then((m) => m.icons),\n  },\n]);","handlingStrategy":"type-guard","validationCode":"function hasIconsOrLoader(x: any): boolean {\n  return (typeof x.loader === 'function') || (x.icons && typeof x.icons === 'object');\n}\nfor (const l of loaders) if (!hasIconsOrLoader(l)) throw new Error(`loader ${l.name} needs icons or loader`);","typeGuard":"function isAsyncIconLoader(x: any): x is { name: string; loader: () => Promise<any> } {\n  return typeof x.name === 'string' && typeof x.loader === 'function';\n}\nfunction isSyncIconLoader(x: any): x is { name: string; icons: object } {\n  return typeof x.name === 'string' && x.icons && typeof x.icons === 'object';\n}","tryCatchPattern":"try { mermaid.registerIconPacks(loaders); } catch (e) { if (/icons or loader/.test(String(e))) console.error('each loader needs icons or loader:', loaders); throw e; }","preventionTips":["Always copy a working example's full object shape including name and loader/icons.","Use the IconLoader union type to get compile-time checks.","Inspect the log.error output that precedes this throw to see the exact bad object."],"tags":["icons","configuration","registericonpacks","validation"],"backgroundTag":null,"analyzedSha":"d93e9c88c01a599c062ee6a3f1462e3558ac6b90","analyzedAt":"2026-08-12T06:23:11.304Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}