tailwindlabs/tailwindcss · error · Error

Unsupported content value: ${pattern}

Error message

Unsupported content value: ${pattern}

What it means

The upgrade codemod that migrates a v3 JS config reads the legacy `content` field and maps each entry to a `{ base, pattern }` source glob. Every pattern must be a string; if a `content.files` entry is an object, number, or array element, the codemod cannot turn it into a glob and throws. This guards against malformed v3 configs that happened to be tolerated by v3's looser runtime.

Source

Thrown at packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts:368

    if (!unresolvedConfig.content) return false
    if (Array.isArray(unresolvedConfig.content)) return false
    if (unresolvedConfig.content.relative) return true
    if (unresolvedConfig.future === 'all') return false
    return unresolvedConfig.future?.relativeContentPathsByDefault ?? false
  })()

  let sourceGlobs = Array.isArray(unresolvedConfig.content)
    ? unresolvedConfig.content.map((pattern) => ({ base, pattern }))
    : (unresolvedConfig.content?.files ?? []).map((pattern) => {
        if (typeof pattern === 'string' && contentIsRelative) {
          return { base: path.dirname(configPath), pattern: pattern }
        }
        return { base, pattern }
      })

  for (let { base, pattern } of sourceGlobs) {
    if (typeof pattern !== 'string') {
      throw new Error('Unsupported content value: ' + pattern)
    }

    let sourceFiles = patternSourceFiles({
      base,
      pattern: pattern[0] === '!' ? pattern.slice(1) : pattern,
      negated: pattern[0] === '!',
    })

    let autoContentContainsAllSourceFiles = true
    for (let sourceFile of sourceFiles) {
      if (!autoContentFiles.includes(sourceFile)) {
        autoContentContainsAllSourceFiles = false
        break
      }
    }

    if (!autoContentContainsAllSourceFiles) {
      sources.push({ base, pattern })

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Edit the v3 config so every `content` / `content.files` entry is a plain string glob before running the upgrade.
  2. Replace `raw` object entries with an actual file the glob can point at.
  3. Re-run `npx @tailwindcss/upgrade` after fixing the config.

Example fix

// before (v3 config)
module.exports = { content: { files: [{ raw: '<div class="p-4">' }] } }

// after
module.exports = { content: ['./src/**/*.html'] }
Defensive patterns

Strategy: validation

Validate before calling

function assertContentStrings(content: unknown): string[] {
  const files = Array.isArray(content) ? content : (content as any)?.files ?? []
  for (const f of files) {
    if (typeof f !== 'string') {
      throw new Error(`Unsupported content entry (must be string): ${JSON.stringify(f)}`)
    }
  }
  return files as string[]
}

Type guard

function isStringContentArray(v: unknown): v is string[] {
  return Array.isArray(v) && v.every(x => typeof x === 'string')
}

Prevention

When it happens

Trigger: A `tailwind.config.js` whose `content` is `{ files: [ { raw: '…' }, './src/**' ] }` (object entries), or `content: ['./x', 42]`. The `typeof pattern !== 'string'` guard at migrate-js-config.ts:368 throws and concatenates the non-string value.

Common situations: Legacy configs using the undocumented `content.files[].raw` object form, or configs programmatically pushing non-string values into `content`.

Related errors


AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12). Data as JSON: /api/errors/172e82ccdcf70445. Report an issue: GitHub.