Developer guide

How generation works under the hood, and how to add providers, change prompts, generate in code and extend the panel.

FreePro

Architecture

FreePro
NamespaceSiteryn_AI_Product_ContentSiteryn_AI_Product_Content_Pro
Bootplugins_loaded, then siteryn_ai_product_content_loadedOn that action; licensed features on init 15, back-end only
Settings optionsiteryn_ai_product_content_settingssiteryn_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.

The shared 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
}

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:

Eventdetail
siteryn-aipc:suggestionitem, type, productId, data, mode
siteryn-aipc:useditem, 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

ActionAccessUse
siteryn_ai_product_content_generateedit_product, nonceproduct_id, type, mode, current, draft[], options[] → {type, format, value, model, cached}
siteryn_ai_product_content_applyedit_product, nonceproduct_id, type, value → {applied, skipped}
siteryn_ai_product_content_ai_connectormanage_woocommerce, noncedo = 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_settingsmanage_woocommerce, the tab form's nonceSave 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, nonceReset to Defaults for one section (tab), then redirects back
Pro: …_pro_bulk, …_pro_review, …_pro_variation, …_pro_category, …_pro_restore, …_pro_license_actionNonce; manage_woocommerce for bulk and license, edit rights on the product or category for the restBulk 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