shadcn-ui/ui · error · Error

Invalid input: not an object literal

Error message

Invalid input: not an object literal

What it means

Thrown by shadcn's tailwind-config updater (ts-morph based) when it cannot find an object literal to mutate. The updater grabs sourceFile.getStatements()[0], expects a VariableStatement whose first declaration's initializer is an ObjectLiteralExpression, and throws if either check fails. It is a hard precondition: shadcn can only patch a config that is a plain exported object literal.

Source

Thrown at packages/shadcn/src/utils/updaters/update-tailwind-config.ts:433

async function parseObjectLiteral(objectLiteralString: string): Promise<any> {
  const sourceFile = await _createSourceFile(
    `const theme = ${objectLiteralString}`,
    null
  )

  const statement = sourceFile.getStatements()[0]
  if (statement?.getKind() === SyntaxKind.VariableStatement) {
    const declaration = (statement as VariableStatement)
      .getDeclarationList()
      ?.getDeclarations()[0]
    const initializer = declaration.getInitializer()
    if (initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) {
      return await parseObjectLiteralExpression(initializer)
    }
  }

  throw new Error("Invalid input: not an object literal")
}

function parseObjectLiteralExpression(node: ObjectLiteralExpression): any {
  const result: any = {}
  for (const property of node.getProperties()) {
    if (property.isKind(SyntaxKind.PropertyAssignment)) {
      const name = property.getName().replace(/\'/g, "")
      if (
        property.getInitializer()?.isKind(SyntaxKind.ObjectLiteralExpression)
      ) {
        result[name] = parseObjectLiteralExpression(
          property.getInitializer() as ObjectLiteralExpression
        )
      } else if (
        property.getInitializer()?.isKind(SyntaxKind.ArrayLiteralExpression)
      ) {
        result[name] = parseArrayLiteralExpression(
          property.getInitializer() as ArrayLiteralExpression

View on GitHub (pinned to efac598707)

Solutions

  1. Rewrite the config so its first top-level statement is a variable/export holding a literal object: `const config = { content: [...], theme: {...} }; export default config` (or `export default { ... }` / `module.exports = { ... }`). No function wrappers, no ternaries, no merge() calls around the object.
  2. Remove any `import`/comment-only or directive first lines so getStatements()[0] is the config variable statement, and move imports above it only if they remain statements before it that are NOT the config (ts-morph indexes the literal first statement, so the config must be statement [0]).
  3. If you need a merged/derived config, build it as a plain object literal first and only assign/merge INTO that literal, then export the literal; do not export the result of a helper call.
  4. Pin/upgrade shadcn to a version matching your Tailwind major (v3 vs v4) so the updater targets the correct file shape.

Example fix

// before
const config = merge(baseConfig, {
  theme: { extend: { colors: {} } }
})
export default config

// after
const config = {
  ...baseConfig,
  theme: { extend: { colors: {} } }
}
export default config
Defensive patterns

Strategy: validation

Validate before calling

// Before calling the shadcn updater, sanity-check the config shape with ts-morph:
import { Project, SyntaxKind } from "ts-morph"
const sf = new Project().addSourceFileAtPath("tailwind.config.ts")
const stmt = sf.getStatements()[0]
const decl = stmt?.getKind() === SyntaxKind.VariableStatement
  ? stmt.getFirstChildByKind(SyntaxKind.VariableDeclarationList)
      ?.getDeclarations()[0]
  : undefined
const ok = decl?.getInitializer()?.isKind(SyntaxKind.ObjectLiteralExpression) ?? false
if (!ok) throw new Error("tailwind config is not a plain object literal; shadcn cannot patch it")

Type guard

// Narrow an exported config node to a patchable object literal.
import { Node, ObjectLiteralExpression, SyntaxKind } from "ts-morph"
function isPatchableObjectLiteral(node: Node | undefined): node is ObjectLiteralExpression {
  return node?.isKind(SyntaxKind.ObjectLiteralExpression) ?? false
}

Try / catch

try {
  await updateTailwindConfig(...) // shadcn call
} catch (e) {
  if (String(e?.message).includes("not an object literal")) {
    throw new Error("tailwind.config must export a plain object literal; rewrite merge()/function wrappers before re-running shadcn.")
  }
  throw e
}

Prevention

When it happens

Trigger: Calling `shadcn` (init/add) against a tailwind.config.{js,ts,mjs,cjs} whose first statement is NOT `const config = { ... }` / `export default { ... }` / `module.exports = { ... }`. Triggered when the config exports a function (e.g. `export default function() { return {...} }`), a conditional/ternary, a Promise, a merge like `module.exports = merge(...)`, an empty file, or a file whose first statement is an import/pragma rather than the config variable.

Common situations: Tailwind v4 setups that use a `@theme` CSS-first config and leave the JS config minimal or function-shaped; configs wrapped in `config({ ... })` helpers (e.g. from `@tailwindcss/typography` or custom wrappers); configs generated by other tools that export a function; monorepos where shadcn picks the wrong config path.

Related errors


AI-assisted analysis of shadcn-ui/ui@efac598707 (2026-08-12). Data as JSON: /api/errors/5638161c9f200fb1. Report an issue: GitHub.