Developer guide
How generation works under the hood, and how to add providers, change prompts, generate in code and extend the panel.
Architecture
| Free | Pro | |
|---|---|---|
| Namespace | Siteryn_AI_Product_Content | Siteryn_AI_Product_Content_Pro |
| Boot | plugins_loaded, then siteryn_ai_product_content_loaded | On that action; licensed features on init 15, back-end only |
| Settings option | siteryn_ai_product_content_settings | siteryn_ai_product_content_pro_settings |
No REST routes, WP-CLI commands, custom tables or front-end output. The pipeline: content type → product context → generation options → prompt (template + system rules) → provider → parsed and sanitized value → panel → apply.
Adding an AI provider
Extend OpenAICompatibleProvider (or AbstractHttpProvider) and register it. It appears on the AI Connector tab automatically, with its own encrypted key.
add_action( 'siteryn_ai_product_content_loaded', function () {
class My_DeepSeek_Provider extends \Siteryn_AI_Product_Content\AI\Providers\OpenAICompatibleProvider {
public function get_id() { return 'deepseek'; }
public function get_label() { return 'DeepSeek'; }
public function get_default_model() { return 'deepseek-chat'; }
public function get_suggested_models() { return array( 'deepseek-chat' ); }
protected function base_url( \Siteryn_AI_Product_Content\AI\ProviderConfig $config ) {
return 'https://api.deepseek.com/v1';
}
}
add_filter( 'siteryn_ai_product_content_providers', function ( $providers ) {
$provider = new My_DeepSeek_Provider();
$providers[ $provider->get_id() ] = $provider;
return $providers;
} );
} );
The array key must equal get_id(). The provider list is cached per request; register before it's first read.
API keys in wp-config.php
define( 'SITERYN_AI_PRODUCT_CONTENT_OPENAI_API_KEY', 'sk-...' ); // one per provider: OPENAI, ANTHROPIC, GEMINI, GROQ, OPENROUTER, MISTRAL define( 'SITERYN_AI_PRODUCT_CONTENT_API_KEY', '...' ); // or one key for every provider except Custom define( 'SITERYN_AI_PRODUCT_CONTENT_CUSTOM_API_KEY', '...' ); define( 'SITERYN_AI_PRODUCT_CONTENT_CUSTOM_BASE_URL', 'https://llm.example.com/v1' ); // the Custom key is only sent to this host
Constants take priority over saved keys and are never stored. Saved keys are encrypted with libsodium using your site's AUTH salt.
SITERYN_AI_PRODUCT_CONTENT_API_KEY is also given to third-party providers you register. Don't combine it with a third-party provider whose endpoint URL admins can change.Generating in code
use Siteryn_AI_Product_Content\Product\ContentGenerator;
use Siteryn_AI_Product_Content\Product\ProductWriter;
$product = wc_get_product( 123 );
$result = ContentGenerator::generate( $product, 'short_description', 'generate', '', get_current_user_id() );
if ( ! is_wp_error( $result ) && ProductWriter::can_edit( $product->get_id() ) ) {
$saved = ProductWriter::apply( $product, $result['type'], $result['value'] ); // {applied, skipped} or WP_Error
}
- Types:
title,short_description,description,excerpt,tags,attributes(Pro addsmeta_title,meta_description; itsvariation_descriptionandcategory_descriptiontypes useContentGenerator::generate_from_context()instead). Modes:generate,rewrite(pass the current text). - Runs with your provider settings, cache and per-user limits.
ProductWriter::apply()doesn't check capabilities: checkcan_edit()first.
Prompt templates
Each content type has a template in templates/prompts/{type}.php that returns generate, rewrite and shape (the expected JSON). Themes can't override them by path; use the siteryn_ai_product_content_template_path filter, or change prompts with the prompt and system_rules filters.
A custom content type
add_filter( 'siteryn_ai_product_content_content_types', function ( $types ) {
$types['faq'] = array( 'label' => 'Product FAQ', 'description' => 'Questions and answers', 'format' => 'html', 'rewritable' => false, 'context' => 'product' );
return $types;
} );
add_filter( 'siteryn_ai_product_content_template_path', function ( $path, $name ) {
return 'prompts/faq.php' === $name ? __DIR__ . '/prompts/faq.php' : $path;
}, 10, 2 );
// prompts/faq.php returns array( 'generate' => '…', 'rewrite' => '', 'shape' => '{"faq": "<p>…</p>"}' )
add_filter( 'siteryn_ai_product_content_apply_faq', function ( $result, $value, $product ) {
$product->update_meta_data( '_my_faq', $value );
return true; // the product is saved after this
}, 10, 3 );
The JSON key must equal the type id. Tick the new type under General → Content Types to show it in the panel.
The excerpt template tag
<?php echo esc_html( siteryn_ai_product_content_get_excerpt() ); // current product, or pass a product/ID ?>
It returns plain text from the _siteryn_ai_product_content_excerpt meta. The meta is registered for the REST API (show_in_rest); writing it needs edit_post on the product.
JavaScript
The panel dispatches bubbling events:
| Event | detail |
|---|---|
siteryn-aipc:suggestion | item, type, productId, data, mode |
siteryn-aipc:used | item, type, productId, value, saved |
Any [data-siteryn-aipc-option="name"] control inside the panel is sent as a generation option; see the example.
AJAX endpoints
| Action | Access | Use |
|---|---|---|
siteryn_ai_product_content_generate | edit_product, nonce | product_id, type, mode, current, draft[], options[] → {type, format, value, model, cached} |
siteryn_ai_product_content_apply | edit_product, nonce | product_id, type, value → {applied, skipped} |
siteryn_ai_product_content_ai_connector | manage_woocommerce, nonce | do = save_key, remove_key, models or test, with provider, key, base_url, model, refresh → {message, provider, statuses, sources}. Never returns a key |
siteryn_ai_product_content_save_settings | manage_woocommerce, the tab form's nonce | Save Changes: option_page plus the option's fields, sanitized by the option's own callback → {message} |
siteryn_ai_product_content_reset_settings (admin-post.php) | manage_woocommerce, nonce | Reset to Defaults for one section (tab), then redirects back |
Pro: …_pro_bulk, …_pro_review, …_pro_variation, …_pro_category, …_pro_restore, …_pro_license_action | Nonce; manage_woocommerce for bulk and license, edit rights on the product or category for the rest | Bulk jobs, review decisions, inline generation, history restore, license activate/deactivate/check |
Pro bulk jobs run on the Action Scheduler hook siteryn_ai_product_content_pro_run_job (group siteryn-ai-product-content-pro), or WP-Cron. They use the optional Bulk Model through siteryn_ai_product_content_provider_config and count requests under user ID 0. The license is re-checked on the twice-daily cron event siteryn_ai_product_content_pro_license_check.
Data
- Free options:
…_settings,…_credentials(encrypted),…_credential_hosts,…_usage(30 days kept),…_connections,…_models_generation,…_cache_generation,…_version(all prefixedsiteryn_ai_product_content). Transientssiteryn_aipc_*for the response cache, per-user hourly counts and model lists (12 hours); short-livedsiteryn_aipc_lock_*options stop duplicate requests. - Free meta:
_siteryn_ai_product_content_excerpt. - Pro:
…_pro_settings,…_pro_jobs,…_pro_version; license optionssiteryn_license_siteryn-ai-product-content-pro_key,_status,_update; meta_siteryn_aipc_pro_history,_pending,_meta_title,_meta_description; transientsiteryn_aipc_pro_sel_{user_id}(Products list selection). Deleting Pro removes all of these. - No custom capabilities. Settings need
manage_woocommerce; generating needsedit_product; new tags needmanage_product_terms.