Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/gonzaloccastro/woocommerce-ai-product-writer/archive/refs/heads/main.zipBuilt as a hands-on project while completing Anthropic's "Building with Claude API" course.
A WordPress plugin that generates WooCommerce product descriptions using Claude (Anthropic) or OpenAI directly from the product editor — no copy-pasting between tabs, no external tools.
The problem
Writing product descriptions is one of the biggest time sinks in WooCommerce catalog management. Existing AI writing tools require leaving WordPress, context-switching to a separate app, and manually copying the result back. I built this to eliminate that friction.
What it does
A metabox on the WooCommerce product editor reads the product name and category, sends them to a configured AI provider, and inserts the generated description directly into the TinyMCE editor in one click.
What I learned building this
This project was the practical application of Anthropic's "Building with Claude API" course. Here are the specific things I had to understand to make it work:
The Messages API structure. Claude uses a messages array where each turn has a role and content. For a single-turn task like this, you send one user message and read content[0].text from the response. The equivalent in OpenAI is choices[0].message.content — structurally similar, different envelope.
The anthropic-version header. Unlike OpenAI, Anthropic requires a version header on every request (anthropic-version: 2023-06-01). Without it, the API rejects the request. This is Anthropic's way of ensuring API contracts don't break when they evolve the API.
Token economics. Every request has input tokens (the prompt you send) and output tokens (what Claude returns). For a task like this — ~100 token prompt, ~60 token output — Claude Haiku costs around $0.0005 per generation. Knowing this lets you make model choices deliberately rather than guessing.
Provider abstraction. The most important architectural decision was isolating all provider-specific logic in class-ai-client.php. The AJAX handler never knows which API is being called. If a client wants to switch from Claude to OpenAI, it's one dropdown change in Settings — no code changes. This is what the course means by "provider-agnostic design."
API key security in WordPress. The course covers key handling in general; adapting it to WordPress required learning about autoload=false in wp_options — ensuring the API key is never loaded in memory on regular page requests, only when a generation is triggered.
Features
- Claude-first, provider-agnostic — defaults to Claude Haiku, also supports OpenAI GPT-4o mini
- Writing tone — Formal, Conversational, or Technical, applied per prompt
- Direct editor insertion — inserts into TinyMCE or plain-text editor, whichever is active
- No Composer required — upload the folder and activate
- WordPress security standards — nonces, capability checks, sanitization, escaping throughout
- Translation-ready — all strings wrapped in
__()/_e()
How the API call works
No SDK, no Python, no intermediary — just a direct HTTP POST from PHP:
// This is the complete Claude API integration
$response = wp_remote_post(
'https://api.anthropic.com/v1/messages',
[
'headers' => [
'Content-Type' => 'application/json',
'x-api-key' => $this->api_key,
'anthropic-version' => '2023-06-01', // required — OpenAI doesn't have this
],
'body' => wp_json_encode([
'model' => 'claude-haiku-4-5-20251001',
'max_tokens' => 300,
'messages' => [
['role' => 'user', 'content' => $prompt]
],
]),
]
);
// Claude's response envelope: content[0].text
$description = trim( $body['content'][0]['text'] );
The API is language-agnostic. What Anthropic's Python SDK does internally is the same thing — build this request and parse this response. PHP can do it natively.
Architecture decisions
Why a separate class-ai-client.php?
The AJAX handler's job is security and routing: verify nonce, check capability, sanitize input, return JSON. It shouldn't know that Claude's response envelope is content[0].text while OpenAI's is choices[0].message.content. That's the client's responsibility. Adding a third provider means adding one case in the client — nothing else changes.
Why autoload=false for the API key?
WordPress autoloads options by default — meaning they're fetched on every request and held in memory. An API key only needs to exist during an AJAX generation call. Storing it with autoload=false ensures it's never in memory on front-end page loads or unrelated admin screens.
Why check_ajax_referer() AND current_user_can()?
A nonce proves the request came from your form. A capability check proves the user can do this action. A logged-in subscriber could craft a POST with a valid nonce — the capability check (edit_products) is what stops them.
Why wp_remote_post() instead of curl?
WordPress's HTTP API respects server proxy and SSL configurations, returns WP_Error on failure (not false), and has a 30-second timeout — intentional, since AI APIs can be slow on first token.
File structure
woocommerce-ai-product-writer/
├── woocommerce-ai-product-writer.php # Plugin headers, constants, boot
├── uninstall.php # Cleans options on deletion
├── includes/
│ ├── class-core.php # Singleton orchestrator
│ ├── class-settings.php # Settings page, option getters
│ ├── class-metabox.php # Metabox registration, asset enqueue
│ └── class-ajax-handler.php # AJAX security, routing, response
├── api/
│ └── class-ai-client.php # Provider abstraction (Claude / OpenAI)
├── admin/
│ ├── views/settings-page.php
│ ├── css/metabox.css
│ └── js/metabox.js
└── languages/
└── woocommerce-ai-product-writer.pot
Requirements
Installation
- Download or clone this repository.
- Upload the
woocommerce-ai-product-writerfolder to/wp-content/plugins/. - Activate from Plugins > Installed Plugins.
- Go to WooCommerce > AI Product Writer.
- Select your provider, paste your API key, choose a tone, and save.
- Open any product — the AI Product Writer metabox appears in the right column.
Security
- All AJAX requests verified with
check_ajax_referer()+current_user_can('edit_products') - API key stored with
autoload=false, never logged, never rendered in the DOM - All
$_POSTinputs sanitized withsanitize_text_field( wp_unslash() ) - All HTML output escaped at point of echo (
esc_html,esc_attr,esc_url) defined('ABSPATH') || exitat the top of every PHP file- Clean uninstall: all options removed when the plugin is deleted
Extending
Add a new provider: add the slug to WCAPW_Settings::VALID_PROVIDERS, add a radio label in render_field_provider(), and add a case + private method in WCAPW_AI_Client.
Change the prompt: edit WCAPW_AI_Client::build_prompt() — isolated from all HTTP logic.
Related
This plugin is part of a portfolio of WordPress + AI integrations built to demonstrate real-world application of the Claude API in a WordPress/WooCommerce context. See also:
payment-links-for-vendors— Custom vendor role + WooCommerce payment links
License
GPL v2 or later — see LICENSE.
Gonzalo Castro — WordPress & WooCommerce developer, Buenos Aires. Working with agencies in Canada and LATAM since 2020.