Telling an agent “please escape your output” once, in one conversation, doesn’t survive a new session, a compacted context window, or a teammate who never saw that message. A skill does. This lesson builds a real Claude Code skill that loads WordPress security and coding-standards expectations automatically, so escaping, sanitization, capability checks, and nonces stop being something you have to remember to ask for.
Claude Code connected from Lesson 2, and a plugin or theme repository on disk, ideally one living inside your local Studio site from Lesson 1 so you can immediately test what gets generated.
Step 1: understand the current skill format
Claude Code’s custom slash commands were originally plain markdown files in
.claude/commands/. Those still work, but the current, recommended format is a
skill: a directory containing a SKILL.md file with YAML frontmatter, placed in
.claude/skills/<skill-name>/SKILL.md for a project, or ~/.claude/skills/ for
something available across every project on your machine. A skill can be invoked
directly with /skill-name, or loaded automatically by Claude when its description
matches what you’re asking for.
Step 2: create the skill directory
# File: terminal, from your plugin's repo root
mkdir -p .claude/skills/wp-secure-code
Project-level, committed to the repo, means every teammate and every fresh Claude Code session working on this plugin gets the same guardrails without having to set anything up themselves.
Step 3: write the skill
---
# File: .claude/skills/wp-secure-code/SKILL.md
name: wp-secure-code
description: WordPress security and coding-standards rules to apply whenever writing or editing PHP in this plugin. Use when generating output that touches user input, database queries, capability checks, or anything rendered to the browser.
---
# WordPress secure coding rules for this plugin
Apply every rule below to any PHP you write or edit in this repository. These are not
suggestions, treat a violation as a bug in your own output before you show it to the
user.
## Escaping output
- Every value printed into HTML must be escaped at the point of output, not the
point of storage. Use `esc_html()` for text content, `esc_attr()` for HTML
attributes, `esc_url()` for URLs, and `wp_kses_post()` only when the value is
expected to contain a limited set of safe HTML.
- Never print a variable directly into a template with a bare `echo $value;` unless
it has already passed through one of the functions above on that same line.
## Sanitizing input
- Every value read from `$_POST`, `$_GET`, `$_REQUEST`, or `$_COOKIE` must be passed
through an appropriate `sanitize_*()` function before use: `sanitize_text_field()`,
`sanitize_email()`, `sanitize_key()`, or `absint()` for expected integers.
- Never trust a value's shape just because JavaScript validated it client-side.
## Database queries
- Every `$wpdb` query containing a variable must use `$wpdb->prepare()`. Never build
a SQL string with string concatenation or interpolation.
## Capability checks
- Any function reachable from an admin-post handler, AJAX handler, or REST callback
must call `current_user_can()` with a specific capability before performing any
state-changing action. Never rely on a nonce check alone, a nonce proves the
request came from your form, not that the requesting user is allowed to do this.
## Nonces
- Any form that changes state must include `wp_nonce_field()`, and the handler that
processes it must verify with `check_admin_referer()` or `wp_verify_nonce()` before
doing anything else.
## Hooks and functions
- Before using any hook or function name you are not certain exists, verify it
against the WordPress core files in this project or developer.wordpress.org.
See the "Why AI Coding Agents Hallucinate WordPress Hooks" lesson for why this
matters.
## Coding standards
- Follow the WordPress PHP Coding Standards (WPCS): tabs for indentation, Yoda
conditions for comparisons involving a constant, and space-padded parentheses,
`if ( $x === true )` not `if ($x === true)`.
- If a PHPCS configuration with the WPCS ruleset exists in this repository, treat its
rules as authoritative over general PHP style preferences.
WordPress Coding Standards is a maintained, installable PHPCS ruleset, not just
documentation. If your plugin’s repo has a phpcs.xml referencing
WordPress-Extra or WordPress-Docs, mention that file’s path in the skill so
Claude checks against the actual configured rules rather than a general impression
of WordPress style.
Step 4: test it with a deliberately risky prompt
Ask for something that would tempt an ungoverned agent to skip escaping or a capability check:
Add an admin-post handler that lets a logged-in user update their display
name from a form field, and show the updated name back on the same page.
Run this once with the skill absent from the repo, and once with it in place. The difference in the first draft Claude Code produces is the actual evidence the skill is doing something, not just documentation you wrote and never checked.
Skill content loads into context and stays there, but a model can still drift, especially across a long session with a lot of other code already generated. Don’t treat “the skill is loaded” as equivalent to “this code is safe.” Lesson 8 in this course gives you the manual review checklist to run regardless of what guardrails were in place during generation.
Recap
A Claude Code skill at .claude/skills/wp-secure-code/SKILL.md, committed to your
plugin’s repository, turns “please escape your output” from a one-off request into a
standing rule every session and every teammate gets automatically. Write the rules as
concrete, checkable statements referencing real WordPress functions, reference your
actual WPCS ruleset where one exists, and verify the skill changes real output with a
before/after test rather than trusting that writing it down was enough.
Resources & further reading
- Extend Claude with skills, code.claude.com
- WordPress Coding Standards, developer.wordpress.org
- WordPress-Coding-Standards PHPCS ruleset, GitHub
- Data Validation, developer.wordpress.org