Claude Code Project Setup: Every Config File Explained

Claude Code’s behavior is shaped by a layered configuration system. There are markdown files that give it instructions, JSON files that control permissions and tooling, and hooks that automate actions at key moments. Getting this right means Claude works the way your team expects from the first session.

This guide covers every configuration file available at the project level — what each one does, where it goes, and whether to commit it. If you’re looking for guidance on writing CLAUDE.md content, AGENTS.md personas, or custom skills, see How to Configure Claude Code with CLAUDE.md, AGENTS.md, and Skills.

The complete project file tree

Here’s every Claude Code configuration file that can exist in a project:

your-project/
  CLAUDE.md                          # Project instructions (committed)
  CLAUDE.local.md                    # Personal instructions (gitignored)
  .mcp.json                          # MCP server config (committed)
  .claude/
    settings.json                    # Project settings (committed)
    settings.local.json              # Personal settings (gitignored)
    commands/                        # Custom slash commands (committed)
      deploy.md
    skills/                          # On-demand skills (committed)
      review/
        SKILL.md
    agents/                          # Subagent definitions (committed)
      researcher.md

You don’t need all of these. Most projects start with just a CLAUDE.md and add more as the need arises.

settings.json — project settings

.claude/settings.json is the structured configuration file for your project. While CLAUDE.md gives Claude natural language instructions, settings.json controls the machinery — permissions, environment variables, hooks, and tool integrations.

Permissions

Permissions control which tools Claude can use without asking. By default, Claude prompts for approval on anything that modifies files or runs commands. You can pre-approve safe operations:

{
  "permissions": {
    "allow": [
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Bash(npm run build)",
      "Read",
      "Write(src/**)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Read(.env)",
      "Read(**/*.pem)"
    ]
  }
}

allow patterns are additive — they stack across all settings levels. deny patterns are absolute — if a tool is denied at any level, no other level can override it. This means your project can deny access to secrets and no individual developer’s local config can undo that.

Permission modes

The defaultMode field sets the baseline permission behavior for the project:

  • default — Claude asks before most actions. Safest for unfamiliar codebases.
  • acceptEdits — Claude can read and write files freely, but still asks before running commands. Good for active development.
  • bypassPermissions — Claude acts without asking. Use with caution, and only when you trust the project context fully.
  • plan — Claude proposes changes but doesn’t execute them until you approve. Good for review workflows.
{
  "defaultMode": "acceptEdits"
}

Environment variables

Inject environment variables into every Claude session for this project:

{
  "env": {
    "NODE_ENV": "development",
    "DATABASE_URL": "postgresql://localhost:5432/myapp_dev"
  }
}

This is useful for setting up development defaults without requiring every developer to configure their shell.

MCP servers

MCP (Model Context Protocol) servers give Claude access to external tools — databases, APIs, monitoring systems, and more. You can define them in settings.json:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "DATABASE_URL": "postgresql://localhost:5432/myapp_dev"
      }
    }
  }
}

However, the preferred location for project MCP servers is .mcp.json at the project root (covered below). Use mcpServers in settings.json when you need to tie server config to permission rules or hooks in the same file.

What to include

A typical project settings.json:

{
  "defaultMode": "acceptEdits",
  "permissions": {
    "allow": [
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Bash(npm run build)",
      "Bash(npx prisma *)"
    ],
    "deny": [
      "Read(.env*)",
      "Read(**/*secret*)",
      "Read(**/*.pem)"
    ]
  },
  "env": {
    "NODE_ENV": "development"
  }
}

Commit this file. It gives the whole team a consistent baseline.

settings.local.json — personal project overrides

.claude/settings.local.json has the same schema as settings.json but is automatically gitignored. It’s for personal configuration that shouldn’t be imposed on the team.

Use it for:

  • Local environment variables — paths, API keys for your dev account, feature flags you’re testing
  • Extra permissions — tools you want auto-approved that others might not
  • Model overrides — if you prefer a different default model for this project
{
  "env": {
    "OPENAI_API_KEY": "sk-...",
    "DEBUG": "true"
  },
  "permissions": {
    "allow": [
      "Bash(docker compose *)"
    ]
  }
}

These settings merge on top of the project settings.json — scalars override, arrays concatenate.

CLAUDE.local.md — personal project instructions

CLAUDE.local.md sits next to CLAUDE.md at the project root and is automatically gitignored. It’s the natural language equivalent of settings.local.json.

Use it for personal context that helps Claude work with you specifically:

I'm focused on the payments module this sprint.
When writing tests, always include edge cases for nil/null inputs.
I prefer detailed explanations when touching code I haven't seen before.

Within the same directory, CLAUDE.local.md is loaded after CLAUDE.md, so your personal instructions take priority when they conflict.

.mcp.json — project MCP servers

.mcp.json at the project root is the dedicated file for defining MCP servers that the whole team shares. It’s the recommended location for project-scoped tool integrations.

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "./docs"]
    }
  }
}

Commit this file, but never hardcode secrets. Use environment variable references (${VAR}) and let each developer set the actual values in their shell or in settings.local.json.

Hooks — event-driven automation

Hooks are shell commands that run at specific points in Claude’s lifecycle. Unlike CLAUDE.md instructions (which are advisory), hooks are guaranteed to execute. Define them in the hooks section of any settings.json.

Hook events

EventWhen it fires
PreToolUseBefore a tool executes
PostToolUseAfter a tool executes
UserPromptSubmitWhen you send a message
StopWhen Claude finishes responding
NotificationWhen Claude sends a notification
SessionStartWhen a session begins
SessionEndWhen a session ends
SubagentStopWhen a subagent finishes
PreCompactBefore context compaction
PostCompactAfter context compaction

Practical examples

Run the linter after every file edit:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx eslint --fix $CLAUDE_FILE_PATH"
          }
        ]
      }
    ]
  }
}

Block writes to protected directories:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo $CLAUDE_FILE_PATH | grep -q '^src/generated/' && echo 'BLOCK: do not modify generated files' && exit 1 || true"
          }
        ]
      }
    ]
  }
}

Hooks are powerful but can break your workflow if misconfigured. Test them carefully, and keep project-level hooks focused on things the whole team benefits from — like linting or safety checks.

The settings hierarchy

Claude Code merges settings from multiple levels. Understanding the priority order prevents surprises.

Settings merge order (lowest to highest priority)

1. User global         ~/.claude/settings.json
2. Project shared      .claude/settings.json
3. Project local       .claude/settings.local.json
4. Enterprise managed  /Library/Application Support/ClaudeCode/managed-settings.json

How merging works:

  • Scalars (strings, numbers, booleans) — higher priority wins
  • Arrays (like permission allow lists) — concatenated across all levels
  • Objects — deep merged
  • Deny rules — if denied at any level, it stays denied everywhere

CLAUDE.md loading order

CLAUDE.md files don’t override each other — they’re all concatenated:

1. Enterprise CLAUDE.md         (if managed deployment)
2. ~/.claude/CLAUDE.md          (your global instructions)
3. Parent directory CLAUDE.md   (walking up from project root)
4. ./CLAUDE.md                  (project instructions)
5. ./CLAUDE.local.md            (your personal instructions)
6. Subdirectory CLAUDE.md       (loaded on-demand)

Within each directory, the local variant is appended after the shared one.

What to commit and what to gitignore

FileCommit?Reason
CLAUDE.mdYesTeam-shared project instructions
CLAUDE.local.mdNoPersonal preferences, auto-gitignored
.claude/settings.jsonYesTeam-shared permissions, hooks, env
.claude/settings.local.jsonNoPersonal overrides, auto-gitignored
.mcp.jsonYesTeam-shared MCP servers (no secrets)
.claude/commands/*.mdYesTeam-shared slash commands
.claude/skills/*/SKILL.mdYesTeam-shared skills
.claude/agents/*.mdYesTeam-shared subagent definitions

The local variants (CLAUDE.local.md and settings.local.json) are automatically gitignored by Claude Code. You don’t need to add them to .gitignore manually.

Managing config files with Crystl

If you’re using Crystl, you don’t need to create or edit these files by hand. Crystl’s File Library gives you a visual editor for every file in the Claude Code config system.

Block editor

The builder opens in a two-column block editor with sections for CLAUDE.md content, rules, and skills. The left pane shows your file as draggable sections; the right pane is a library of reusable blocks you can insert with one click. Drag blocks into any area — instructions, rules, or skills — to compose a complete configuration package. You can then install the whole package into a project at once, writing the CLAUDE.md, .claude/rules/, and .claude/commands/ files in one step.

You can also expand blocks to edit their content, toggle between structured block mode and raw text mode, and drag to reorder within any section.

A live line count in the footer turns yellow at 150 lines and red over 200 — Claude Code’s recommended limit for CLAUDE.md. When you hit red, move content into rules instead.

File chooser and templates

When creating a new gem, Crystl shows a panel with every supported file type — CLAUDE.md, CLAUDE.local.md, AGENTS.md, settings.json, and more. Each entry shows whether the file already exists in the current project. Select a type, pick a template from your library, and Crystl writes it to the right location.

The recommended option is CLAUDE.md w/rules, which creates both a CLAUDE.md with scaffold sections and a .claude/rules/ directory for modular instructions.

Project optimizer

The optimizer scans your project and flags configuration gaps — missing CLAUDE.md, no build commands section, files over 200 lines, missing settings.json. Each recommendation explains why it matters and offers a button to create the missing file.

Setting up a new project

Here’s a practical sequence for configuring Claude Code on a new project:

1. Start with CLAUDE.md

Run /init in a Claude Code session to generate a starter file, or create one through Crystl’s file chooser which gives you scaffold templates with pre-built sections. Cover your stack, build commands, and the conventions that matter most. Keep it under 200 lines — use .claude/rules/ for the rest.

2. Add project settings

Create .claude/settings.json with permissions for your common commands. Pre-approving npm test and npm run lint removes friction without sacrificing safety.

3. Wire up MCP servers if needed

If your project uses databases, external APIs, or monitoring tools that Claude should access, add a .mcp.json. Start small — one or two servers — and expand as the team finds it useful.

4. Add hooks for guardrails

If there are actions that should always happen (linting after edits) or never happen (writing to generated files), add hooks. These are more reliable than CLAUDE.md instructions for critical rules.

5. Let local config handle the rest

Tell your team about CLAUDE.local.md and settings.local.json for personal preferences. Each developer can tune their experience without affecting anyone else.

Tips

  • Start minimal. A five-line CLAUDE.md and a basic settings.json with permission allows is enough to start. Add complexity as you discover what’s needed.
  • Treat CLAUDE.md like code. Review changes to it in PRs. Bad instructions create bad behavior for the whole team.
  • Use deny rules for security. If your project has .env files, API keys, or certificates, deny read access at the project level. This protects the whole team regardless of individual settings.
  • Keep secrets out of committed files. Use ${ENV_VAR} references in .mcp.json and put actual values in settings.local.json or your shell environment.
  • Update as you go. When Claude makes a mistake because it didn’t know a convention, add it to CLAUDE.md. The file should grow organically from real issues, not from trying to anticipate every scenario upfront.