byanthropics· 237 skills
This skill should be used when the user asks about "plugin settings", "store plugin configuration", "user-configurable plugin", ".local.md files", "plugin state files", "read YAML frontmatter", "per-project plugin settings", or wants to make plugin behavior configurable. Documents the .claude/plugin-name.local.md pattern for storing plugin-specific configuration with YAML frontmatter and markdown content.
$npx -y skills add anthropics/claude-code --skill plugin-settingsInstalls into the current project.
Run `npx skills use "https://github.com/anthropics/claude-code" --skill "anthropics/claude-code/plugin-settings"` and follow the generated skill instructions now. Read its complete output, redirecting it to a temporary file first if necessary. Resolve relative paths from the supporting-files directory it provides.
Use the skills in "https://github.com/anthropics/claude-code" that are relevant to the current task. Run `npx skills add "https://github.com/anthropics/claude-code"` and select the relevant skills, then follow their instructions.
| 1 | # Plugin Settings Pattern for Claude Code Plugins |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | **Key characteristics:** |
| 8 | - File location: `.claude/plugin-name.local.md` in project root |
| 9 | - Structure: YAML frontmatter + markdown body |
| 10 | - Purpose: Per-project plugin configuration and state |
| 11 | - Usage: Read from hooks, commands, and agents |
| 12 | - Lifecycle: User-managed (not in git, should be in `.gitignore`) |
| 13 | |
| 14 | ## File Structure |
| 15 | |
| 16 | ### Basic Template |
| 17 | |
| 18 | ```markdown |
| 19 | --- |
| 20 | enabled: true |
| 21 | setting1: value1 |
| 22 | setting2: value2 |
| 23 | numeric_setting: 42 |
| 24 | list_setting: ["item1", "item2"] |
| 25 | --- |
| 26 | |
| 27 | # Additional Context |
| 28 | |
| 29 | This markdown body can contain: |
| 30 | - Task descriptions |
| 31 | - Additional instructions |
| 32 | - Prompts to feed back to Claude |
| 33 | - Documentation or notes |
| 34 | ``` |
| 35 | |
| 36 | ### Example: Plugin State File |
| 37 | |
| 38 | **.claude/my-plugin.local.md:** |
| 39 | ```markdown |
| 40 | --- |
| 41 | enabled: true |
| 42 | strict_mode: false |
| 43 | max_retries: 3 |
| 44 | notification_level: info |
| 45 | coordinator_session: team-leader |
| 46 | --- |
| 47 | |
| 48 | # Plugin Configuration |
| 49 | |
| 50 | This plugin is configured for standard validation mode. |
| 51 | Contact @team-lead with questions. |
| 52 | ``` |
| 53 | |
| 54 | ## Reading Settings Files |
| 55 | |
| 56 | ### From Hooks (Bash Scripts) |
| 57 | |
| 58 | **Pattern: Check existence and parse frontmatter** |
| 59 | |
| 60 | ```bash |
| 61 | #!/bin/bash |
| 62 | set -euo pipefail |
| 63 | |
| 64 | # Define state file path |
| 65 | STATE_FILE=".claude/my-plugin.local.md" |
| 66 | |
| 67 | # Quick exit if file doesn't exist |
| 68 | if [[ ! -f "$STATE_FILE" ]]; then |
| 69 | exit 0 # Plugin not configured, skip |
| 70 | fi |
| 71 | |
| 72 | # Parse YAML frontmatter (between --- markers) |
| 73 | FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE") |
| 74 | |
| 75 | # Extract individual fields |
| 76 | ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/') |
| 77 | STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/') |
| 78 | |
| 79 | # Check if enabled |
| 80 | if [[ "$ENABLED" != "true" ]]; then |
| 81 | exit 0 # Disabled |
| 82 | fi |
| 83 | |
| 84 | # Use configuration in hook logic |
| 85 | if [[ "$STRICT_MODE" == "true" ]]; then |
| 86 | # Apply strict validation |
| 87 | # ... |
| 88 | fi |
| 89 | ``` |
| 90 | |
| 91 | See `examples/read-settings-hook.sh` for complete working example. |
| 92 | |
| 93 | ### From Commands |
| 94 | |
| 95 | Commands can read settings files to customize behavior: |
| 96 | |
| 97 | ```markdown |
| 98 | --- |
| 99 | description: Process data with plugin |
| 100 | allowed-tools: ["Read", "Bash"] |
| 101 | --- |
| 102 | |
| 103 | # Process Command |
| 104 | |
| 105 | Steps: |
| 106 | 1. Check if settings exist at `.claude/my-plugin.local.md` |
| 107 | 2. Read configuration using Read tool |
| 108 | 3. Parse YAML frontmatter to extract settings |
| 109 | 4. Apply settings to processing logic |
| 110 | 5. Execute with configured behavior |
| 111 | ``` |
| 112 | |
| 113 | ### From Agents |
| 114 | |
| 115 | Agents can reference settings in their instructions: |
| 116 | |
| 117 | ```markdown |
| 118 | --- |
| 119 | name: configured-agent |
| 120 | description: Agent that adapts to project settings |
| 121 | --- |
| 122 | |
| 123 | Check for plugin settings at `.claude/my-plugin.local.md`. |
| 124 | If present, parse YAML frontmatter and adapt behavior according to: |
| 125 | - enabled: Whether plugin is active |
| 126 | - mode: Processing mode (strict, standard, lenient) |
| 127 | - Additional configuration fields |
| 128 | ``` |
| 129 | |
| 130 | ## Parsing Techniques |
| 131 | |
| 132 | ### Extract Frontmatter |
| 133 | |
| 134 | ```bash |
| 135 | # Extract everything between --- markers |
| 136 | FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE") |
| 137 | ``` |
| 138 | |
| 139 | ### Read Individual Fields |
| 140 | |
| 141 | **String fields:** |
| 142 | ```bash |
| 143 | VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/') |
| 144 | ``` |
| 145 | |
| 146 | **Boolean fields:** |
| 147 | ```bash |
| 148 | ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//') |
| 149 | # Compare: if [[ "$ENABLED" == "true" ]]; then |
| 150 | ``` |
| 151 | |
| 152 | **Numeric fields:** |
| 153 | ```bash |
| 154 | MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//') |
| 155 | # Use: if [[ $MAX -gt 100 ]]; then |
| 156 | ``` |
| 157 | |
| 158 | ### Read Markdown Body |
| 159 | |
| 160 | Extract content after second `---`: |
| 161 | |
| 162 | ```bash |
| 163 | # Get everything after closing --- |
| 164 | BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE") |
| 165 | ``` |
| 166 | |
| 167 | ## Common Patterns |
| 168 | |
| 169 | ### Pattern 1: Temporarily Active Hooks |
| 170 | |
| 171 | Use settings file to control hook activation: |
| 172 | |
| 173 | ```bash |
| 174 | #!/bin/bash |
| 175 | STATE_FILE=".claude/security-scan.local.md" |
| 176 | |
| 177 | # Quick exit if not configured |
| 178 | if [[ ! -f "$STATE_FILE" ]]; then |
| 179 | exit 0 |
| 180 | fi |
| 181 | |
| 182 | # Read enabled flag |
| 183 | FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE") |
| 184 | ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//') |
| 185 | |
| 186 | if [[ "$ENABLED" != "true" ]]; then |
| 187 | exit 0 # Disabled |
| 188 | fi |
| 189 | |
| 190 | # Run hook logic |
| 191 | # ... |
| 192 | ``` |
| 193 | |
| 194 | **Use case:** Enable/disable hooks without editing hooks.json (requires restart). |
| 195 | |
| 196 | ### Pattern 2: Agent State Management |
| 197 | |
| 198 | Store agent-specific state and configuration: |
| 199 | |
| 200 | **.claude/multi-agent-swarm.local.md:** |
| 201 | ```markdown |
| 202 | --- |
| 203 | agent_name: auth-agent |
| 204 | task_number: 3.5 |
| 205 | pr_number: 1234 |
| 206 | coordinator_session: team-leader |
| 207 | enabled: true |
| 208 | dependencies: ["Task 3.4"] |
| 209 | --- |
| 210 | |
| 211 | # Task Assignment |
| 212 | |
| 213 | Implement JWT authentication for the API. |
| 214 | |
| 215 | **Success Criteria:** |
| 216 | - Authentication endpoints created |
| 217 | - Tests passing |
| 218 | - PR created and CI green |
| 219 | ``` |
| 220 | |
| 221 | Read from hooks to coordinate agents: |
| 222 | |
| 223 | ```bash |
| 224 | AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//') |
| 225 | COORDINATOR=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//') |
| 226 | |
| 227 | # Send notification to coordinator |
| 228 | tmux send-keys -t "$COORDINATOR" "Agent $AGENT_NAME completed task" Enter |
| 229 | ``` |
| 230 | |
| 231 | ### Pattern 3: Configuration-Driven Behavior |
| 232 | |
| 233 | **.claude/my-plugin.local.md:** |
| 234 | ```markdown |
| 235 | --- |
| 236 | validation_level: strict |
| 237 | max_file_size: 1000000 |
| 238 | allowed_extensions: [".js", ".ts", ".tsx"] |
| 239 | enable_logging: true |
| 240 | --- |
| 241 | |
| 242 | # Validation Configuration |
| 243 | |
| 244 | Strict mode enabled for this project. |
| 245 | All writes validated against security policies. |
| 246 | ``` |
| 247 | |
| 248 | Use in hooks or commands: |
| 249 | |
| 250 | ```bash |
| 251 | LEVEL=$(echo "$FRONTMATTER" | grep '^validation_level:' | sed 's/validation_level: *//') |
| 252 | |
| 253 | case "$LEVEL" in |
| 254 | strict) |
| 255 | # Apply strict validation |
| 256 | ;; |
| 257 | standard) |
| 258 | # Apply standard validation |
| 259 | ;; |
| 260 | lenient) |
| 261 | # Apply lenient validation |
| 262 | ;; |
| 263 | esac |
| 264 | ``` |
| 265 | |
| 266 | ## Creating Settings Files |
| 267 | |
| 268 | ### From Commands |
| 269 | |
| 270 | Commands can create settings files: |
| 271 | |
| 272 | ```markdown |
| 273 | # Setup Command |
| 274 | |
| 275 | Steps: |
| 276 | 1. Ask user for configuration preferences |
| 277 | 2. Create `.claude/my-plugin.local.md` with YAML frontmatter |
| 278 | 3. Set appropriate values based on user input |
| 279 | 4. Inform user that settings are saved |
| 280 | 5. Remind user to restart Claude Code for hooks to recognize changes |
| 281 | ``` |
| 282 | |
| 283 | ### Template Generation |
| 284 | |
| 285 | Provide template in plugin README: |
| 286 | |
| 287 | ```markdown |
| 288 | ## Configuration |
| 289 | |
| 290 | Create `.claude/my-plugin.local.md` in your project: |
| 291 | |
| 292 | \`\`\`markdown |
| 293 | --- |
| 294 | enabled: true |
| 295 | mode: standard |
| 296 | max_retries: 3 |
| 297 | --- |
| 298 | |
| 299 | # Plugin Configuration |
| 300 | |
| 301 | Your settings are active. |
| 302 | \`\`\` |
| 303 | |
| 304 | After creating or editing, restart Claude Code for changes to take effect. |
| 305 | ``` |
| 306 | |
| 307 | ## Best Practices |
| 308 | |
| 309 | ### File Naming |
| 310 | |
| 311 | ✅ **DO:** |
| 312 | - Use `.claude/plugin-name.local.md` format |
| 313 | - Match plugin name exactly |
| 314 | - Use `.local.md` suffix for user-local files |
| 315 | |
| 316 | ❌ **DON'T:** |
| 317 | - Use different directory (not `.claude/`) |
| 318 | - Use inconsistent naming |
| 319 | - Use `.md` without `.local` (might be committed) |
| 320 | |
| 321 | ### Gitignore |
| 322 | |
| 323 | Always add to `.gitignore`: |
| 324 | |
| 325 | ```gitignore |
| 326 | .claude/*.local.md |
| 327 | .claude/*.local.json |
| 328 | ``` |
| 329 | |
| 330 | Document this in plugin README. |
| 331 | |
| 332 | ### Defaults |
| 333 | |
| 334 | Provide sensible defaults when settings file doesn't exist: |
| 335 | |
| 336 | ```bash |
| 337 | if [[ ! -f "$STATE_FILE" ]]; then |
| 338 | # Use defaults |
| 339 | ENABLED=true |
| 340 | MODE=standard |
| 341 | else |
| 342 | # Read from file |
| 343 | # ... |
| 344 | fi |
| 345 | ``` |
| 346 | |
| 347 | ### Validation |
| 348 | |
| 349 | Validate settings values: |
| 350 | |
| 351 | ```bash |
| 352 | MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//') |
| 353 | |
| 354 | # Validate numeric range |
| 355 | if ! [[ "$MAX" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then |
| 356 | echo "⚠️ Invalid max_value in settings (must be 1-100)" >&2 |
| 357 | MAX=10 # Use default |
| 358 | fi |
| 359 | ``` |
| 360 | |
| 361 | ### Restart Requirement |
| 362 | |
| 363 | **Important:** Settings changes require Claude Code restart. |
| 364 | |
| 365 | Document in your README: |
| 366 | |
| 367 | ```markdown |
| 368 | ## Changing Settings |
| 369 | |
| 370 | After editing `.claude/my-plugin.local.md`: |
| 371 | 1. Save the file |
| 372 | 2. Exit Claude Code |
| 373 | 3. Restart: `claude` or `cc` |
| 374 | 4. New settings will be loaded |
| 375 | ``` |
| 376 | |
| 377 | Hooks cannot be hot-swapped within a session. |
| 378 | |
| 379 | ## Security Considerations |
| 380 | |
| 381 | ### Sanitize User Input |
| 382 | |
| 383 | When writing settings files from user input: |
| 384 | |
| 385 | ```bash |
| 386 | # Escape quotes in user input |
| 387 | SAFE_VALUE=$(echo "$USER_INPUT" | sed 's/"/\\"/g') |
| 388 | |
| 389 | # Write to file |
| 390 | cat > "$STATE_FILE" <<EOF |
| 391 | --- |
| 392 | user_setting: "$SAFE_VALUE" |
| 393 | --- |
| 394 | EOF |
| 395 | ``` |
| 396 | |
| 397 | ### Validate File Paths |
| 398 | |
| 399 | If settings contain file paths: |
| 400 | |
| 401 | ```bash |
| 402 | FILE_PATH=$(echo "$FRONTMATTER" | grep '^data_file:' | sed 's/data_file: *//') |
| 403 | |
| 404 | # Check for path traversal |
| 405 | if [[ "$FILE_PATH" == *".."* ]]; then |
| 406 | echo "⚠️ Invalid path in settings (path traversal)" >&2 |
| 407 | exit 2 |
| 408 | fi |
| 409 | ``` |
| 410 | |
| 411 | ### Permissions |
| 412 | |
| 413 | Settings files should be: |
| 414 | - Readable by user only (`chmod 600`) |
| 415 | - Not committed to git |
| 416 | - Not shared between users |
| 417 | |
| 418 | ## Real-World Examples |
| 419 | |
| 420 | ### multi-agent-swarm Plugin |
| 421 | |
| 422 | **.claude/multi-agent-swarm.local.md:** |
| 423 | ```markdown |
| 424 | --- |
| 425 | agent_name: auth-implementation |
| 426 | task_number: 3.5 |
| 427 | pr_number: 1234 |
| 428 | coordinator_session: team-leader |
| 429 | enabled: true |
| 430 | dependencies: ["Task 3.4"] |
| 431 | additional_instructions: Use JWT tokens, not sessions |
| 432 | --- |
| 433 | |
| 434 | # Task: Implement Authentication |
| 435 | |
| 436 | Build JWT-based authentication for the REST API. |
| 437 | Coordinate with auth-agent on shared types. |
| 438 | ``` |
| 439 | |
| 440 | **Hook usage (agent-stop-notification.sh):** |
| 441 | - Checks if file exists (line 15-18: quick exit if not) |
| 442 | - Parses frontmatter to get coordinator_session, agent_name, enabled |
| 443 | - Sends notifications to coordinator if enabled |
| 444 | - Allows quick activation/deactivation via `enabled: true/false` |
| 445 | |
| 446 | ### ralph-wiggum Plugin |
| 447 | |
| 448 | **.claude/ralph-loop.local.md:** |
| 449 | ```markdown |
| 450 | --- |
| 451 | iteration: 1 |
| 452 | max_iterations: 10 |
| 453 | completion_promise: "All tests passing and build successful" |
| 454 | --- |
| 455 | |
| 456 | Fix all the linting errors in the project. |
| 457 | Make sure tests pass after each fix. |
| 458 | ``` |
| 459 | |
| 460 | **Hook usage (stop-hook.sh):** |
| 461 | - Checks if file exists (line 15-18: quick exit if not active) |
| 462 | - Reads iteration count and max_iterations |
| 463 | - Extracts completion_promise for loop termination |
| 464 | - Reads body as the prompt to feed back |
| 465 | - Updates iteration count on each loop |
| 466 | |
| 467 | ## Quick Reference |
| 468 | |
| 469 | ### File Location |
| 470 | |
| 471 | ``` |
| 472 | project-root/ |
| 473 | └── .claude/ |
| 474 | └── plugin-name.local.md |
| 475 | ``` |
| 476 | |
| 477 | ### Frontmatter Parsing |
| 478 | |
| 479 | ```bash |
| 480 | # Extract frontmatter |
| 481 | FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE") |
| 482 | |
| 483 | # Read field |
| 484 | VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/') |
| 485 | ``` |
| 486 | |
| 487 | ### Body Parsing |
| 488 | |
| 489 | ```bash |
| 490 | # Extract body (after second ---) |
| 491 | BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE") |
| 492 | ``` |
| 493 | |
| 494 | ### Quick Exit Pattern |
| 495 | |
| 496 | ```bash |
| 497 | if [[ ! -f ".claude/my-plugin.local.md" ]]; then |
| 498 | exit 0 # Not configured |
| 499 | fi |
| 500 | ``` |
| 501 | |
| 502 | ## Additional Resources |
| 503 | |
| 504 | ### Reference Files |
| 505 | |
| 506 | For detailed implementation patterns: |
| 507 | |
| 508 | - **`references/parsing-techniques.md`** - Complete guide to parsing YAML frontmatter and markdown bodies |
| 509 | - **`references/real-world-examples.md`** - Deep dive into multi-agent-swarm and ralph-wiggum implementations |
| 510 | |
| 511 | ### Example Files |
| 512 | |
| 513 | Working examples in `examples/`: |
| 514 | |
| 515 | - **`read-settings-hook.sh`** - Hook that reads and uses settings |
| 516 | - **`create-settings-command.md`** - Command that creates settings file |
| 517 | - **`example-settings.md`** - Template settings file |
| 518 | |
| 519 | ### Utility Scripts |
| 520 | |
| 521 | Development tools in `scripts/`: |
| 522 | |
| 523 | - **`validate-settings.sh`** - Validate settings file structure |
| 524 | - **`parse-frontmatter.sh`** - Extract frontmatter fields |
| 525 | |
| 526 | ## Implementation Workflow |
| 527 | |
| 528 | To add settings to a plugin: |
| 529 | |
| 530 | 1. Design settings schema (which fields, types, defaults) |
| 531 | 2. Create template file in plugin documentation |
| 532 | 3. Add gitignore entry for `.claude/*.local.md` |
| 533 | 4. Implement settings parsing in hooks/commands |
| 534 | 5. Use quick-exit pattern (check file exists, check enabled field) |
| 535 | 6. Document settings in plugin README with template |
| 536 | 7. Remind users that changes require Claude Code restart |
| 537 | |
| 538 | Focus on keeping settings simple and providing good defaults when settings file doesn't exist. |