shadcn-ui/ui · error · Error

Cannot write to ${filePath}: path exists and is a directory.

Error message

Cannot write to ${filePath}: path exists and is a directory. Please provide a file path instead.

What it means

Thrown during file installation in update-files.ts when the resolved filePath already exists and statSync(filePath).isDirectory() is true. shadcn cannot overwrite a directory with a file, so it aborts with a clear message rather than producing a partial/corrupt write.

Source

Thrown at packages/shadcn/src/utils/updaters/update-files.ts:159

      }
    }

    const existingFile = existsSync(filePath)

    // TODO: revisit this when we implement utils transform instead of override.
    if (
      file.type === "registry:lib" &&
      basename(file.path) === "utils.ts" &&
      projectInfo?.framework.name === "laravel" &&
      existingFile
    ) {
      filesSkipped.push(path.relative(config.resolvedPaths.cwd, filePath))
      continue
    }

    // Check if the path exists and is a directory - we can't write to directories.
    if (existingFile && statSync(filePath).isDirectory()) {
      throw new Error(
        `Cannot write to ${filePath}: path exists and is a directory. Please provide a file path instead.`
      )
    }

    // Run our transformers.
    // Skip transformers for .env files to preserve exact content
    // Skip transformers for universal item files (registry:file and registry:item)
    // to preserve their original content as they're meant to be framework-agnostic
    const isUniversalItemFile =
      file.type === "registry:file" || file.type === "registry:item"
    const content =
      isEnvFile(filePath) || isUniversalItemFile
        ? file.content
        : await transform(
            {
              filename: file.path,
              raw: file.content,
              config,

View on GitHub (pinned to efac598707)

Solutions

  1. Inspect the filePath in the message; rename or remove the existing directory that occupies that path.
  2. Adjust the registry item's file.path or target so it does not collide with an existing folder.
  3. If the directory is intentional, change the item's path to write into a file inside it (e.g. "utils/index.ts").
  4. Re-run the add command after resolving the collision.

Example fix

# before — ./src/lib/utils exists as a directory, item wants utils.ts at ./src/lib/utils
# rename the folder
mv src/lib/utils src/lib/utils-dir
# or fix the item path to utils/index.ts
Defensive patterns

Strategy: validation

Validate before calling

import fs from "fs"
function assertFileTargetIsNotDir(filePath: string) {
  if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
    throw new Error(`${filePath} is a directory`)
  }
}

Type guard

const isWritableFilePath = (p: string) =>
  !fs.existsSync(p) || !fs.statSync(p).isDirectory()

Try / catch

try {
  await updateFiles(tree, config, options)
} catch (e) {
  if (e instanceof Error && /path exists and is a directory/.test(e.message)) {
    // rename the conflicting directory and retry
  }
  throw e
}

Prevention

When it happens

Trigger: For each file in the resolved tree, resolveFilePath computes the destination; if that path currently exists and is a directory (e.g. an item's file.path collides with an existing folder name), the guard throws before any transformer or write runs.

Common situations: A registry item's file path (e.g. "utils") collides with an existing directory of the same name; a previous install created a folder where a file is now expected; Laravel/Next.js routing folders like "[slug]" clashing; target alias resolving a file onto a directory.

Related errors


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