Sanitizing on save and escaping again on output are both required, doing only one leaves either a stored-XSS risk or a broken display for legitimately tricky input like an ampersand in a URL. Treat them as two separate steps, not one.
Code
Build a custom meta box with proper output sanitization
An event-details meta box scenario covering nonce and autosave guards on save, per-field sanitization, and avoiding the classic recursive save_post loop.
Works with Claude / GPT1,050 uses★ 4.3
The prompt
code-custom-meta-box-sanitization
I need a meta box on the "Event" custom post type edit screen with three fields: event date (date picker), ticket price (number), and an external registration URL. CONTEXT: [a previous developer's version of this saved raw $_POST values directly with no sanitization, and separately caused the edit screen to hang/error on autosave, which is why I'm rebuilding it properly] Build this the way a senior dev handles meta box save logic correctly, in order: 1. Show `add_meta_box()` registered on `add_meta_boxes`, and the render callback outputting a nonce field via `wp_nonce_field()` alongside the three inputs, populated from existing meta with `esc_attr( get_post_meta(...) )` so re-editing doesn't break on values containing quotes or special characters. 2. Show the `save_post` callback with the full guard sequence, explained not just listed: verify the nonce first, then check `current_user_can( 'edit_post', $post_id )`, then bail early on `wp_is_post_autosave()` and `wp_is_post_revision()`, since skipping any of these either creates a security hole or causes the exact autosave crash the previous version had (autosave requests don't include the same POST fields, so code that assumes they do can throw notices or overwrite data with empty values). 3. Sanitize each field with the correct function for its type, not a blanket `sanitize_text_field()` for everything: the date with a validated format check (reject anything that doesn't parse as a real date rather than silently storing garbage), the price with `absint()` or a float-safe equivalent with a minimum of zero, and the URL with `esc_url_raw()` before saving, `esc_url()` only on output. 4. Avoid the classic recursive loop: since `save_post` fires on every save including the one your own `update_post_meta()` calls might trigger indirectly through other hooked code, show the pattern of removing and re-adding the action inside the callback if there's any risk of `wp_update_post()` being called from within it. 5. Flag the edge case: if this event post type supports Gutenberg, confirm the meta box still renders (older-style `add_meta_box()` boxes show in a "Advanced" panel collapsed by default in the block editor), and consider whether these fields should actually be registered via `register_post_meta()` with `show_in_rest` and a proper sidebar panel instead, for a cleaner editing experience. Replace the fields and post type with your actual meta box needs.