AI

OpenAI in WordPress and WooCommerce: Rapidtextai and custom AI tools

AI drafting inside the WordPress editor and WooCommerce admin, built around Rapidtextai. Every output lands as a draft or meta field, never straight on the live site.

Client
Content-heavy WordPress and WooCommerce sites (several client deployments plus the Rapidtextai plugin)
My role
Plugin author and lead WordPress/AI engineer
  • WordPress
  • WooCommerce
  • OpenAI API
  • Rapidtextai
  • Action Scheduler
  • Block editor
  • Embeddings
  • PHP
AI generations per month across client sites
8,000+

AI generations per month across client sites

SKUs drafted in about 4 hours instead of about 3 weeks
≈ 2,500

SKUs drafted in about 4 hours instead of about 3 weeks

monthly API spend cut by per-role caps (first client)
≈ 35%

monthly API spend cut by per-role caps (first client)

chat questions answered without a human, CSAT ≈ 4.3/5
≈ 62%

chat questions answered without a human, CSAT ≈ 4.3/5

The challenge

Content teams were buried in product copy and blog briefs. Marketing wanted AI drafting inside the tools they already used, not another SaaS tab to log into.

The hard requirements were brand-voice control, cost guardrails, and output that does not read like a robot. Finance also had to be able to see and limit what the AI was costing.

What I built

  1. 01

    Multi-model routing

    One settings screen lets the user pick a model per task: a cheap model for bulk product descriptions, a stronger model for long-form blog drafts, and an image model for featured images. The model list is fetched dynamically, so new API models appear without a plugin update.

  2. 02

    Role-based access and credit budgets

    Site admins set a monthly generation cap per role (editor, author, shop manager). Usage is logged per user and per post, with a dashboard showing spend and tokens. This was the feature that got the plugin approved by finance at two client sites.

  3. 03

    Bulk WooCommerce description generator

    Action Scheduler runs it in batches of 50 products. Each result is written to a meta field rather than the live product, and a review queue sits in front of publishing.

  4. 04

    Brief-to-draft auto-posting

    A topic row in a custom table generates an outline, then the sections, then a draft post with a featured image and SEO meta, flagged for human review. It never publishes directly.

  5. 05

    Editor integration and prompt library

    Generate, Rewrite and Expand buttons sit in the block editor toolbar and the classic editor, and a shortcode provides front-end AI forms. Prompts live in the database, are editable per site, and use {{brand_voice}} and {{product_category}} tokens so output stays on-brand.

  6. 06

    Category-aware WooCommerce prompts

    In other projects, product descriptions use category-aware prompts and handle variations: generate once per parent, inherit to the variations, with per-variation tweaks. A regenerate action keeps the human-edited version.

  7. 07

    Support chat over the client's docs

    Embeddings are stored in a custom table, the top-k matches are retrieved, then a completion call runs with a strict "answer only from context" system prompt and a fallback to a human handoff.

  8. 08

    Images and repurposing

    Blog featured images and OG cards are generated with a moderation check before they are sideloaded into the media library. Long posts are repurposed into social captions, a meta description and an email teaser, all written as post meta for review.

Gotchas and how I fixed them

  • Truncated output looks like success

    When the model hits max_tokens the API still returns HTTP 200, so a cut-off description would be saved as if it were finished. Checking finish_reason for "length" and returning an error instead fixes it.

  • Bulk runs must not overwrite human edits

    Generated text goes to a draft meta field, and a _description_human_edited flag makes the job skip any product a person has already touched. A human still has the final click.

  • Rate limits should queue, not fail

    A 429 response schedules a retry through Action Scheduler a minute later instead of failing the request.

Code from the build

Safe OpenAI call with timeout, retry and finish_reason checkphp
function my_ai_completion( string $prompt, string $model = 'gpt-4o-mini', int $max_tokens = 800 ): string|WP_Error {
    $body = [
        'model'       => $model,
        'messages'    => [
            [ 'role' => 'system', 'content' => 'You are a helpful product copywriter. Answer only from the provided context.' ],
            [ 'role' => 'user',   'content' => $prompt ],
        ],
        'max_tokens'  => $max_tokens,
        'temperature' => 0.7,
    ];

    $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', [
        'timeout' => 60,
        'headers' => [
            'Authorization' => 'Bearer ' . MY_OPENAI_KEY,
            'Content-Type'  => 'application/json',
        ],
        'body'    => wp_json_encode( $body ),
    ] );

    if ( is_wp_error( $response ) ) {
        return $response;
    }

    $code = wp_remote_retrieve_response_code( $response );
    $data = json_decode( wp_remote_retrieve_body( $response ), true );

    if ( 429 === $code ) {
        // Rate limited — schedule a retry rather than failing the request.
        as_schedule_single_action( time() + 60, 'my_ai_retry', [ $prompt, $model ] );
        return new WP_Error( 'ai_rate_limited', 'Rate limited; queued for retry.' );
    }

    if ( 200 !== $code ) {
        return new WP_Error( 'ai_api_error', $data['error']['message'] ?? 'Unknown API error' );
    }

    $choice = $data['choices'][0] ?? null;
    if ( ! $choice ) {
        return new WP_Error( 'ai_empty', 'No completion returned.' );
    }

    if ( 'length' === ( $choice['finish_reason'] ?? '' ) ) {
        return new WP_Error( 'ai_truncated', 'Output hit max_tokens; increase the limit or split the prompt.' );
    }

    return trim( $choice['message']['content'] );
}

The finish_reason check is the most important line, because without it truncated output looks like a successful completion and gets saved as a broken description.

Draft-first product description writerphp
add_action( 'my_generate_product_description', function ( int $product_id ): void {
    $product = wc_get_product( $product_id );
    if ( ! $product ) {
        return;
    }

    // Never overwrite a human edit.
    if ( '1' === get_post_meta( $product_id, '_description_human_edited', true ) ) {
        return;
    }

    $prompt = sprintf(
        "Write a 120-word product description for: %s. Category: %s. Price: %s. Tone: %s. Return only the description.",
        $product->get_name(),
        wp_strip_all_tags( wc_get_product_category_list( $product_id ) ),
        $product->get_price(),
        get_option( 'my_brand_tone', 'professional and warm' )
    );

    $result = my_ai_completion( $prompt );
    if ( is_wp_error( $result ) ) {
        update_post_meta( $product_id, '_ai_description_error', $result->get_error_message() );
        return;
    }

    // Store as a draft meta field; the editor publishes deliberately.
    update_post_meta( $product_id, '_ai_description_draft', wp_kses_post( $result ) );
    update_post_meta( $product_id, '_ai_description_generated_at', time() );
}, 10, 1 );

Writing to a meta draft instead of $product->set_description() is what makes the tool safe to run in bulk, since a human still has the final click.

Per-role monthly usage capphp
function my_ai_user_can_generate( int $user_id ): bool {
    $cap  = (int) get_option( 'my_ai_monthly_cap_' . my_ai_user_role( $user_id ), 100 );
    $used = (int) get_user_meta( $user_id, '_ai_generations_' . gmdate( 'Ym' ), true );
    return $used < $cap;
}

function my_ai_record_generation( int $user_id ): void {
    $key  = '_ai_generations_' . gmdate( 'Ym' );
    $used = (int) get_user_meta( $user_id, $key, true );
    update_user_meta( $user_id, $key, $used + 1 );
}

The cap is keyed on year-month, so it resets by itself each month without a cron job.

Results

  • 8,000+ AI generations per month across client sites, peaking at ≈ 1.2M tokens per day on the bulk product pipeline.
  • ≈ 2,500 SKUs drafted in ≈ 4 hours versus ≈ 3 weeks of copywriting; review time came down to ≈ 15 minutes per 50 products.
  • Content team output rose ≈ 3× with the same headcount, and time to first draft for a blog post fell from ≈ 2 hours to ≈ 10 minutes.
  • Per-role caps cut monthly API spend by ≈ 35% on the first client after their first month of unconstrained usage.
  • The chat widget answered ≈ 62% of inbound questions without human escalation, with CSAT of ≈ 4.3/5 on those conversations.
  • Zero published-post incidents from auto-generation, because every AI output lands in draft or meta and never goes straight to publish.

Questions people ask

Can OpenAI write WooCommerce product descriptions in bulk without publishing mistakes?

Yes. The bulk generator runs through Action Scheduler in batches of 50 products and writes each result to a draft meta field, not the live product. A review queue sits before publish, and products a person has already edited are skipped. One run drafted about 2,500 SKUs in about 4 hours.

How do I stop a WordPress AI plugin from running up my OpenAI bill?

Set a monthly generation cap per role (editor, author, shop manager) and log usage per user and per post. On the first client, these caps reduced monthly spend by about 35% after the first month of unconstrained use.

Can I use different AI models for different tasks in WordPress?

Yes. Rapidtextai has one settings screen where you choose a model per task: a cheap model for bulk product descriptions, a stronger one for long blog drafts, and an image model for featured images. The model list is fetched live, so new models show up without a plugin update.

Can an AI chat widget answer questions only from my own documentation?

Yes. Your docs are stored as embeddings in a custom table, the top matches are retrieved, and the completion call uses a strict "answer only from context" system prompt with a fallback to a human. This setup answered about 62% of questions without escalation.

Will AI-generated posts publish themselves on my site?

No. The auto-posting pipeline builds an outline, sections, a featured image and SEO meta, then saves a draft flagged for human review. There have been zero published-post incidents from auto-generation.