Plugin Settings Pattern
Per-project plugin configuration using .claude/plugin-name.local.md files with YAML frontmatter for structured settings and markdown body for additional context.
When to Use This Skill
Use plugin settings when... Use alternatives when...
Plugin needs per-project configuration Settings are global (use ~/.claude/settings.json )
Hooks need runtime enable/disable control Hook behavior is always-on
Agent state persists between sessions State is ephemeral within a session
Users customize plugin behavior per-project Plugin has no configurable behavior
Configuration includes prose/prompts alongside structured data All config is purely structured (use .json )
File Structure
Location
project-root/ └── .claude/ └── plugin-name.local.md # Per-project, user-local settings
Format
enabled: true mode: standard max_retries: 3 allowed_extensions: [".js", ".ts", ".tsx"]
Additional Context
Markdown body for prompts, instructions, or documentation that hooks and agents can read and use.
Naming Convention
-
Use .claude/plugin-name.local.md format
-
Match the plugin name exactly from plugin.json
-
The .local.md suffix signals user-local (not committed to git)
Gitignore
Add to project .gitignore :
.claude/*.local.md
Reading Settings
From Shell Scripts (Hooks)
Use the standard frontmatter extraction pattern from .claude/rules/shell-scripting.md :
#!/bin/bash set -euo pipefail
STATE_FILE=".claude/my-plugin.local.md"
Quick exit if not configured
[[ -f "$STATE_FILE" ]] || exit 0
Extract field using standard pattern
extract_field() { local file="$1" field="$2" head -50 "$file" | grep -m1 "^${field}:" | sed 's/^[^:]:[[:space:]]//' | tr -d '\r' }
plugin_enabled=$(extract_field "$STATE_FILE" "enabled") [[ "$plugin_enabled" == "true" ]] || exit 0
plugin_mode=$(extract_field "$STATE_FILE" "mode")
Extract Markdown Body
Get content after the closing --- frontmatter delimiter
BODY=$(awk '/^---$/{i++; next} i>=2' "$STATE_FILE")
From Skills and Agents
Skills and agents read settings with the Read tool:
- Check if
.claude/my-plugin.local.mdexists - Read the file and parse YAML frontmatter
- Apply settings to current behavior
- Use markdown body as additional context/prompt
Common Patterns
Pattern 1: Toggle-Based Hook Activation
Control hook activation without editing hooks.json :
#!/bin/bash set -euo pipefail STATE_FILE=".claude/security-scan.local.md" [[ -f "$STATE_FILE" ]] || exit 0
extract_field() { local file="$1" field="$2" head -50 "$file" | grep -m1 "^${field}:" | sed 's/^[^:]:[[:space:]]//' | tr -d '\r' }
scan_enabled=$(extract_field "$STATE_FILE" "enabled") [[ "$scan_enabled" == "true" ]] || exit 0
Hook logic runs only when enabled
Pattern 2: Agent State Between Sessions
Store agent task state for multi-session work:
agent_name: auth-implementation task_number: 3.5 pr_number: 1234 enabled: true
Current Task
Implement JWT authentication for the REST API. Coordinate with auth-agent on shared types.
Pattern 3: Configuration-Driven Validation
validation_level: strict max_file_size: 1000000 allowed_extensions: [".js", ".ts", ".tsx"]
validation_level=$(extract_field "$STATE_FILE" "validation_level") case "$validation_level" in strict) run_strict_checks ;; standard) run_standard_checks ;; *) run_standard_checks ;; # Default esac
Implementation Checklist
When adding settings to a plugin:
-
Design settings schema (fields, types, defaults)
-
Create template in plugin README
-
Add .claude/*.local.md to .gitignore
-
Implement parsing using extract_field pattern
-
Use quick-exit pattern ([[ -f "$STATE_FILE" ]] || exit 0 )
-
Provide sensible defaults when file is missing
-
Document that changes require Claude Code restart (hooks only)
Best Practices
Practice Details
Quick exit Check file existence first, exit 0 if absent
Sensible defaults Provide fallback values when settings file is missing
Use extract_field
Standard frontmatter extraction from shell-scripting.md
Validate values Check numeric ranges, enum membership
File permissions Settings files should be user-readable only (chmod 600 )
Restart notice Document that hook-related changes need a Claude Code restart
Agentic Optimizations
Context Command
Check settings exist [[ -f ".claude/plugin.local.md" ]]
Extract single field head -50 file | grep -m1 "^field:" | sed 's/^[^:]:[[:space:]]//'
Extract body awk '/^---$/{i++; next} i>=2' file
Quick enable check [[ "$(extract_field file enabled)" == "true" ]]