Plugin Settings Pattern for Claude Code Plugins
Overview
Plugins can store user-configurable settings and state in .claude/plugin-name.local.md files within the project directory. This pattern uses YAML frontmatter for structured configuration and markdown content for prompts or additional context.
Key characteristics:
- File location:
.claude/plugin-name.local.md in project root
- Structure: YAML frontmatter + markdown body
- Purpose: Per-project plugin configuration and state
- Usage: Read from hooks, commands, and agents
- Lifecycle: User-managed (not in git, should be in
.gitignore)
File Structure
Basic Template
markdown
1---
2enabled: true
3setting1: value1
4setting2: value2
5numeric_setting: 42
6list_setting: ["item1", "item2"]
7---
8
9# Additional Context
10
11This markdown body can contain:
12
13- Task descriptions
14- Additional instructions
15- Prompts to feed back to Claude
16- Documentation or notes
Example: Plugin State File
.claude/my-plugin.local.md:
markdown
1---
2enabled: true
3strict_mode: false
4max_retries: 3
5notification_level: info
6coordinator_session: team-leader
7---
8
9# Plugin Configuration
10
11This plugin is configured for standard validation mode.
12Contact @team-lead with questions.
Reading Settings Files
From Hooks (Bash Scripts)
Pattern: Check existence and parse frontmatter
bash
1#!/bin/bash
2set -euo pipefail
3
4# Define state file path
5STATE_FILE=".claude/my-plugin.local.md"
6
7# Quick exit if file doesn't exist
8if [[ ! -f "$STATE_FILE" ]]; then
9 exit 0 # Plugin not configured, skip
10fi
11
12# Parse YAML frontmatter (between --- markers)
13FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
14
15# Extract individual fields
16ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/')
17STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/')
18
19# Check if enabled
20if [[ "$ENABLED" != "true" ]]; then
21 exit 0 # Disabled
22fi
23
24# Use configuration in hook logic
25if [[ "$STRICT_MODE" == "true" ]]; then
26 # Apply strict validation
27 # ...
28fi
See examples/read-settings-hook.sh for complete working example.
From Commands
Commands can read settings files to customize behavior:
markdown
1---
2description: Process data with plugin
3allowed-tools: ["Read", "Bash"]
4---
5
6# Process Command
7
8Steps:
9
101. Check if settings exist at `.claude/my-plugin.local.md`
112. Read configuration using Read tool
123. Parse YAML frontmatter to extract settings
134. Apply settings to processing logic
145. Execute with configured behavior
From Agents
Agents can reference settings in their instructions:
markdown
1---
2name: configured-agent
3description: Agent that adapts to project settings
4---
5
6Check for plugin settings at `.claude/my-plugin.local.md`.
7If present, parse YAML frontmatter and adapt behavior according to:
8
9- enabled: Whether plugin is active
10- mode: Processing mode (strict, standard, lenient)
11- Additional configuration fields
Parsing Techniques
bash
1# Extract everything between --- markers
2FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
Read Individual Fields
String fields:
bash
1VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')
Boolean fields:
bash
1ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
2# Compare: if [[ "$ENABLED" == "true" ]]; then
Numeric fields:
bash
1MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
2# Use: if [[ $MAX -gt 100 ]]; then
Read Markdown Body
Extract content after second ---:
bash
1# Get everything after closing ---
2BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
Common Patterns
Pattern 1: Temporarily Active Hooks
Use settings file to control hook activation:
bash
1#!/bin/bash
2STATE_FILE=".claude/security-scan.local.md"
3
4# Quick exit if not configured
5if [[ ! -f "$STATE_FILE" ]]; then
6 exit 0
7fi
8
9# Read enabled flag
10FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
11ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
12
13if [[ "$ENABLED" != "true" ]]; then
14 exit 0 # Disabled
15fi
16
17# Run hook logic
18# ...
Use case: Enable/disable hooks without editing hooks.json (requires restart).
Pattern 2: Agent State Management
Store agent-specific state and configuration:
.claude/multi-agent-swarm.local.md:
markdown
1---
2agent_name: auth-agent
3task_number: 3.5
4pr_number: 1234
5coordinator_session: team-leader
6enabled: true
7dependencies: ["Task 3.4"]
8---
9
10# Task Assignment
11
12Implement JWT authentication for the API.
13
14**Success Criteria:**
15
16- Authentication endpoints created
17- Tests passing
18- PR created and CI green
Read from hooks to coordinate agents:
bash
1AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//')
2COORDINATOR=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//')
3
4# Send notification to coordinator
5tmux send-keys -t "$COORDINATOR" "Agent $AGENT_NAME completed task" Enter
Pattern 3: Configuration-Driven Behavior
.claude/my-plugin.local.md:
markdown
1---
2validation_level: strict
3max_file_size: 1000000
4allowed_extensions: [".js", ".ts", ".tsx"]
5enable_logging: true
6---
7
8# Validation Configuration
9
10Strict mode enabled for this project.
11All writes validated against security policies.
Use in hooks or commands:
bash
1LEVEL=$(echo "$FRONTMATTER" | grep '^validation_level:' | sed 's/validation_level: *//')
2
3case "$LEVEL" in
4 strict)
5 # Apply strict validation
6 ;;
7 standard)
8 # Apply standard validation
9 ;;
10 lenient)
11 # Apply lenient validation
12 ;;
13esac
Creating Settings Files
From Commands
Commands can create settings files:
markdown
1# Setup Command
2
3Steps:
4
51. Ask user for configuration preferences
62. Create `.claude/my-plugin.local.md` with YAML frontmatter
73. Set appropriate values based on user input
84. Inform user that settings are saved
95. Remind user to restart Claude Code for hooks to recognize changes
Template Generation
Provide template in plugin README:
markdown
1## Configuration
2
3Create `.claude/my-plugin.local.md` in your project:
4
5## \`\`\`markdown
6
7enabled: true
8mode: standard
9max_retries: 3
10
11---
12
13# Plugin Configuration
14
15Your settings are active.
16\`\`\`
17
18After creating or editing, restart Claude Code for changes to take effect.
Best Practices
File Naming
✅ DO:
- Use
.claude/plugin-name.local.md format
- Match plugin name exactly
- Use
.local.md suffix for user-local files
❌ DON'T:
- Use different directory (not
.claude/)
- Use inconsistent naming
- Use
.md without .local (might be committed)
Gitignore
Always add to .gitignore:
gitignore
1.claude/*.local.md
2.claude/*.local.json
Document this in plugin README.
Defaults
Provide sensible defaults when settings file doesn't exist:
bash
1if [[ ! -f "$STATE_FILE" ]]; then
2 # Use defaults
3 ENABLED=true
4 MODE=standard
5else
6 # Read from file
7 # ...
8fi
Validation
Validate settings values:
bash
1MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
2
3# Validate numeric range
4if ! [[ "$MAX" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then
5 echo "⚠️ Invalid max_value in settings (must be 1-100)" >&2
6 MAX=10 # Use default
7fi
Restart Requirement
Important: Settings changes require Claude Code restart.
Document in your README:
markdown
1## Changing Settings
2
3After editing `.claude/my-plugin.local.md`:
4
51. Save the file
62. Exit Claude Code
73. Restart: `claude` or `cc`
84. New settings will be loaded
Hooks cannot be hot-swapped within a session.
Security Considerations
When writing settings files from user input:
bash
1# Escape quotes in user input
2SAFE_VALUE=$(echo "$USER_INPUT" | sed 's/"/\\"/g')
3
4# Write to file
5cat > "$STATE_FILE" <<EOF
6---
7user_setting: "$SAFE_VALUE"
8---
9EOF
Validate File Paths
If settings contain file paths:
bash
1FILE_PATH=$(echo "$FRONTMATTER" | grep '^data_file:' | sed 's/data_file: *//')
2
3# Check for path traversal
4if [[ "$FILE_PATH" == *".."* ]]; then
5 echo "⚠️ Invalid path in settings (path traversal)" >&2
6 exit 2
7fi
Permissions
Settings files should be:
- Readable by user only (
chmod 600)
- Not committed to git
- Not shared between users
Real-World Examples
multi-agent-swarm Plugin
.claude/multi-agent-swarm.local.md:
markdown
1---
2agent_name: auth-implementation
3task_number: 3.5
4pr_number: 1234
5coordinator_session: team-leader
6enabled: true
7dependencies: ["Task 3.4"]
8additional_instructions: Use JWT tokens, not sessions
9---
10
11# Task: Implement Authentication
12
13Build JWT-based authentication for the REST API.
14Coordinate with auth-agent on shared types.
Hook usage (agent-stop-notification.sh):
- Checks if file exists (line 15-18: quick exit if not)
- Parses frontmatter to get coordinator_session, agent_name, enabled
- Sends notifications to coordinator if enabled
- Allows quick activation/deactivation via
enabled: true/false
ralph-wiggum Plugin
.claude/ralph-loop.local.md:
markdown
1---
2iteration: 1
3max_iterations: 10
4completion_promise: "All tests passing and build successful"
5---
6
7Fix all the linting errors in the project.
8Make sure tests pass after each fix.
Hook usage (stop-hook.sh):
- Checks if file exists (line 15-18: quick exit if not active)
- Reads iteration count and max_iterations
- Extracts completion_promise for loop termination
- Reads body as the prompt to feed back
- Updates iteration count on each loop
Quick Reference
File Location
project-root/
└── .claude/
└── plugin-name.local.md
Frontmatter Parsing
bash
1# Extract frontmatter
2FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
3
4# Read field
5VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')
Body Parsing
bash
1# Extract body (after second ---)
2BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
Quick Exit Pattern
bash
1if [[ ! -f ".claude/my-plugin.local.md" ]]; then
2 exit 0 # Not configured
3fi
Additional Resources
Reference Files
For detailed implementation patterns:
references/parsing-techniques.md - Complete guide to parsing YAML frontmatter and markdown bodies
references/real-world-examples.md - Deep dive into multi-agent-swarm and ralph-wiggum implementations
Example Files
Working examples in examples/:
read-settings-hook.sh - Hook that reads and uses settings
create-settings-command.md - Command that creates settings file
example-settings.md - Template settings file
Utility Scripts
Development tools in scripts/:
validate-settings.sh - Validate settings file structure
parse-frontmatter.sh - Extract frontmatter fields
Implementation Workflow
To add settings to a plugin:
- Design settings schema (which fields, types, defaults)
- Create template file in plugin documentation
- Add gitignore entry for
.claude/*.local.md
- Implement settings parsing in hooks/commands
- Use quick-exit pattern (check file exists, check enabled field)
- Document settings in plugin README with template
- Remind users that changes require Claude Code restart
Focus on keeping settings simple and providing good defaults when settings file doesn't exist.