{"record":{"id":"f182b4db8977e4d8","repo":"facebook/docusaurus","slug":"json-stringify-val-is-not-a-valid-html-tag-obj","errorCode":null,"errorMessage":"${JSON.stringify(val)} is not a valid HTML tag object. \"tagName\" must be defined as a string.","messagePattern":"(.+?) is not a valid HTML tag object\\. \"tagName\" must be defined as a string\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/docusaurus/src/server/htmlTags.ts","lineNumber":27,"sourceCode":"import htmlTags from 'html-tags';\nimport voidHtmlTags from 'html-tags/void';\nimport escapeHTML from 'escape-html';\nimport type {\n  Props,\n  HtmlTagObject,\n  HtmlTags,\n  LoadedPlugin,\n  RouterType,\n} from '@docusaurus/types';\n\n// TODO this should be done at config validation time, not here\nfunction assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject {\n  if (typeof val !== 'object' || !val) {\n    throw new Error(`\"${val}\" is not a valid HTML tag object.`);\n  }\n  const htmlTag = val as HtmlTagObject;\n  if (typeof htmlTag.tagName !== 'string') {\n    throw new Error(\n      `${JSON.stringify(\n        val,\n      )} is not a valid HTML tag object. \"tagName\" must be defined as a string.`,\n    );\n  }\n  if (\n    !htmlTag.customElement &&\n    !(htmlTags as string[]).includes(htmlTag.tagName)\n  ) {\n    throw new Error(\n      `Error loading ${JSON.stringify(val)}, \"${\n        htmlTag.tagName\n      }\" is not a valid HTML tag. Either use a valid \"tagName\" or set \"customElement: true\".`,\n    );\n  }\n}\n\nfunction hashRouterAbsoluteToRelativeTagAttribute(","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/facebook/docusaurus/blob/3f483e80e326cc646b54b83d564b3f0c4881b9a6/packages/docusaurus/src/server/htmlTags.ts#L9-L45","documentation":"Thrown by `assertIsHtmlTagObject` when the value IS an object but its `tagName` property is missing or not a string. This is the second-stage check after the object-shape check; it pins the failure precisely to the `tagName` field.","triggerScenarios":"Passing an object like `{attributes: {...}}` or `{tagName: 123}` into a tags array — i.e. an object that survived the first guard but lacks a string `tagName`. Triggered at htmlTags.ts:27 when `typeof htmlTag.tagName !== 'string'`.","commonSituations":"Building a tag object dynamically and forgetting the `tagName` key; renaming `tagName` to `tag` or `name`; using a numeric or symbol tagName.","solutions":["Add a string `tagName` to the object (e.g. `'meta'`, `'script'`, `'link'`).","If constructing tags from data, validate `typeof obj.tagName === 'string'` before pushing into the array.","Use the string-shorthand form (`'<meta .../>'`) instead of the object form for static tags."],"exampleFix":"// before\nscripts: [{attributes: {src: '/x.js'}}],\n// after\nscripts: [{tagName: 'script', attributes: {src: '/x.js'}}],","handlingStrategy":"type-guard","validationCode":"function assertTagsHaveName(tags: unknown[]) {\n  tags.forEach((t, i) => {\n    if (typeof t === 'object' && t !== null && typeof (t as any).tagName !== 'string') {\n      throw new Error(`Tag at index ${i} is missing a string tagName`);\n    }\n  });\n}","typeGuard":"function isHtmlTagObject(v: unknown): v is {tagName: string; attributes?: Record<string,string>} {\n  return typeof v === 'object' && v !== null && typeof (v as any).tagName === 'string';\n}","tryCatchPattern":null,"preventionTips":["Use the `HtmlTagObject` type from `@docusaurus/types` for all tag objects.","Prefer the string-shorthand form for static tags to avoid shape mistakes.","Unit-test plugin tag generators before wiring into config."],"tags":["config","html-tags","validation"],"backgroundTag":null,"analyzedSha":"3f483e80e326cc646b54b83d564b3f0c4881b9a6","analyzedAt":"2026-08-12T13:25:04.382Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}