{"record":{"id":"f758ca0f427f5d65","repo":"tailwindlabs/tailwindcss","slug":"matchutilities-name-defines-an-inv","errorCode":null,"errorMessage":"`matchUtilities({ '${name}' : … })` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter, eg. `scrollbar`.","messagePattern":"`matchUtilities\\((.+?)\\)` defines an invalid utility name\\. Utilities should be alphanumeric and start with a lowercase letter, eg\\. `scrollbar`\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tailwindcss/src/compat/plugin-api.ts","lineNumber":366,"sourceCode":"        designSystem.utilities.static(className, (candidate) => {\n          let clonedAst = ast.map(cloneAstNode)\n          replaceNestedClassNameReferences(clonedAst, className, candidate.raw)\n          featuresRef.current |= substituteAtApply(clonedAst, designSystem)\n          return clonedAst\n        })\n      }\n    },\n\n    matchUtilities(utilities, options) {\n      let types = options?.type\n        ? Array.isArray(options?.type)\n          ? options.type\n          : [options.type]\n        : ['any']\n\n      for (let [name, fn] of Object.entries(utilities)) {\n        if (!IS_VALID_UTILITY_NAME.test(name)) {\n          throw new Error(\n            `\\`matchUtilities({ '${name}' : … })\\` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter, eg. \\`scrollbar\\`.`,\n          )\n        }\n\n        function compileFn({ negative }: { negative: boolean }) {\n          return (candidate: Extract<Candidate, { kind: 'functional' }>) => {\n            // Throw out any candidate whose value is not a supported type\n            if (\n              candidate.value?.kind === 'arbitrary' &&\n              types.length > 0 &&\n              !types.includes('any')\n            ) {\n              // The candidate has an explicit data type but it's not in the list\n              // of supported types by this utility. For example, a `scrollbar`\n              // utility that is only used to change the scrollbar color but is\n              // used with a `length` value: `scrollbar-[length:var(--whatever)]`\n              if (candidate.value.dataType && !types.includes(candidate.value.dataType)) {\n                return","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/tailwindlabs/tailwindcss/blob/16e94cbf7f965c5ad697e90e940b5e178efad67c/packages/tailwindcss/src/compat/plugin-api.ts#L348-L384","documentation":"Thrown by the compatibility layer's `matchUtilities` API when a key in the `utilities` object fails the `IS_VALID_UTILITY_NAME` regex (`/^[a-z@][a-zA-Z0-9/%._-]*$/`). Unlike `addUtilities` selectors, `matchUtilities` keys are bare names (no leading dot) that become functional utilities like `name-[value]`, so they must be alphanumeric-friendly and start with a lowercase letter (or @). An invalid name cannot be compiled into a candidate, so registration is aborted.","triggerScenarios":"A plugin calls `matchUtilities({ 'Scroll': fn })` (capital first letter), `matchUtilities({ '1col': fn })` (leading digit), `matchUtilities({ 'col width': fn })` (space, not in allowed set), or `matchUtilities({ '_foo': fn })` (leading underscore — underscore is allowed only after the first char). The check runs for every entry via `Object.entries(utilities)`, so one bad key fails the whole call.","commonSituations":"Migrating a v3 plugin whose utility name began with an underscore or capital; copy-paste from a config object that used display labels; plugin authors assuming `matchUtilities` accepts the same selector syntax as `addUtilities`.","solutions":["Rename the utility key to start with a lowercase letter and use only `[a-zA-Z0-9/%._-]` afterwards, e.g. `scroll`, `col-span`.","Strip any leading dot, space, or underscore from the key — `matchUtilities` wants the bare name, not a selector.","If you need a negative or prefixed variant, rely on the `options.negative` / theme prefix machinery rather than baking symbols into the name.","Validate every key with `/^[a-z@][a-zA-Z0-9/%._-]*$/.test(name)` in your plugin before calling `matchUtilities`."],"exampleFix":"// before\nmatchUtilities({\n  'Scrollbar': (value) => ({ scrollbarColor: value }),\n})\n// after\nmatchUtilities({\n  'scrollbar': (value) => ({ scrollbarColor: value }),\n})","handlingStrategy":"validation","validationCode":"const IS_VALID_UTILITY_NAME = /^[a-z@][a-zA-Z0-9/%._-]*$/;\nfunction safeMatchUtilities(matchUtilities, utilities, options) {\n  const filtered = {};\n  for (const [name, fn] of Object.entries(utilities)) {\n    if (IS_VALID_UTILITY_NAME.test(name)) {\n      filtered[name] = fn;\n    } else {\n      console.warn(`Skipping invalid matchUtilities name: ${JSON.stringify(name)}`);\n    }\n  }\n  matchUtilities(filtered, options);\n}","typeGuard":"function isValidUtilityName(name: string): boolean {\n  return /^[a-z@][a-zA-Z0-9/%._-]*$/.test(name);\n}","tryCatchPattern":"try {\n  matchUtilities(utilities, options);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('invalid utility name')) {\n    // filter and retry, or warn\n  } else throw e;\n}","preventionTips":["Name matchUtilities keys as bare lowercase-first identifiers.","Reject keys containing spaces, leading dots, or leading underscores in CI lint.","Document the allowed character set in your plugin readme."],"tags":["tailwind-v4","plugin-api","matchutilities","validation","compat"],"backgroundTag":null,"analyzedSha":"16e94cbf7f965c5ad697e90e940b5e178efad67c","analyzedAt":"2026-08-12T06:02:42.469Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}