# CLAUDE.md: WordPress plugin

## Commands
- Local site: `npx wp-env start` · WP-CLI: `npx wp-env run cli wp plugin list`
- Lint: `vendor/bin/phpcs --standard=WordPress` · Tests: `vendor/bin/phpunit` (WP test suite) · JS: `npm run build` (`@wordpress/scripts`)

## Structure
- Main file `my-plugin.php` with the plugin header; logic in namespaced classes under `includes/` (Composer autoload). Guard every PHP file with `defined( 'ABSPATH' ) || exit;`.
- Prefix everything global (functions, options, hooks, CPT slugs, script handles) with the plugin slug, or use a PHP namespace.
- Hook into WordPress instead of running code at include time (`add_action( 'init', ... )`).

## Security rules (non-negotiable)
- Capability check on every privileged action: `current_user_can( 'manage_options' )`.
- Nonces on every form and AJAX/REST mutation: `wp_nonce_field` + `check_admin_referer`, or `wp_verify_nonce`.
- Sanitize input (`sanitize_text_field`, `absint`, `sanitize_email`, `wp_kses_post`) and escape output late (`esc_html`, `esc_attr`, `esc_url`, `wp_kses`).
- SQL only via `$wpdb->prepare()`; prefer WP APIs (`WP_Query`, `get_posts`, options, meta) over raw SQL.
- REST routes via `register_rest_route` with a real `permission_callback` (never `__return_true` for writes) and `args` validation.

## Other
- Enqueue assets with `wp_enqueue_script/style` and the generated `*.asset.php` dependencies; never hard-code `<script>` tags.
- Make strings translatable: `__( 'Text', 'my-plugin' )`, `esc_html__`.
- Clean up on uninstall in `uninstall.php`, not on deactivation.
