Most Gemini API tutorials assume Python or JavaScript. But PHP still runs the overwhelming majority of the web, including every WordPress site, and calling Gemini from PHP is genuinely simple once you see the request shape. No SDK required: the REST API and ~30 lines of code are all you need.
In this post you’ll get a working Gemini client in plain PHP, a WordPress-native variant using wp_remote_post(), and the error-handling and cost-control habits that separate a demo from something you can ship.
Last tested/updated: 2026-10-16. Model ID, endpoint, headers and request and response fields checked against Google’s current Gemini API reference; both PHP functions linted and run against a sample response.
Get an API key
Head to Google AI Studio (aistudio.google.com), sign in, and create an API key. The free tier is generous enough for all the experiments in this series, with one catch: Google uses free-tier prompts and responses to improve its products, so test with sample text, not client data. Two rules before we write any code:
- Never hardcode the key in a file that could end up in a public repo. Use an environment variable or, in WordPress, a constant in
wp-config.php. - Treat the key like a password. Anyone holding it can spend your quota.
The request shape
Every text generation call is a POST to the generateContent endpoint of a model:
POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent
The body is JSON: an array of contents, each with parts. The response comes back as candidates, each with the same content/parts structure. That symmetry (parts in, parts out) is the core mental model for the whole API, and it’s what later makes multimodal input (images, PDFs) and function calling feel natural.
Two things changed since I first wrote this. Google now recommends its newer Interactions API (/v1beta/interactions) for new development, but says generateContent “remains fully supported”, and it is still the simplest request to learn the API with. And the Gemini 2.5 models are now served only to projects that already used them, so new projects should start on gemini-3.8-flash, the current stable Flash model.
Plain PHP with cURL
<?php
function gemini_generate( string $prompt, string $model = 'gemini-3.8-flash' ): string {
$api_key = getenv( 'GEMINI_API_KEY' );
if ( ! $api_key ) {
throw new RuntimeException( 'GEMINI_API_KEY is not set.' );
}
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent";
$body = [
'contents' => [
[ 'parts' => [ [ 'text' => $prompt ] ] ],
],
];
$ch = curl_init( $url );
curl_setopt_array( $ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-goog-api-key: ' . $api_key,
],
CURLOPT_POSTFIELDS => json_encode( $body ),
CURLOPT_TIMEOUT => 60,
] );
$response = curl_exec( $ch );
$status = curl_getinfo( $ch, CURLINFO_RESPONSE_CODE );
if ( false === $response ) {
throw new RuntimeException( 'Gemini request failed: ' . curl_error( $ch ) );
}
if ( 200 !== $status ) {
throw new RuntimeException( "Gemini API error (HTTP {$status}): " . $response );
}
$data = json_decode( $response, true );
$text = '';
foreach ( $data['candidates'][0]['content']['parts'] ?? [] as $part ) {
if ( empty( $part['thought'] ) && isset( $part['text'] ) ) {
$text .= $part['text'];
}
}
return $text;
}
echo gemini_generate( 'Explain WordPress hooks in two sentences.' );
Run it with GEMINI_API_KEY=your-key php test.php and you have a working AI integration in one function. It needs PHP 8.0 or later. There is no curl_close(): it has done nothing since PHP 8.0 and is deprecated in PHP 8.5. The loop joins every text part and skips any part marked thought, so the function keeps working if you later turn on thought summaries.
The WordPress-native version
Inside WordPress, use the HTTP API instead of raw cURL: you inherit proxy support, filters, and consistent error objects:
function myplugin_gemini_generate( string $prompt ): string|WP_Error {
if ( ! defined( 'MYPLUGIN_GEMINI_KEY' ) ) {
return new WP_Error( 'no_api_key', 'Gemini API key is not configured.' );
}
$response = wp_remote_post(
'https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent',
[
'timeout' => 60,
'headers' => [
'Content-Type' => 'application/json',
'x-goog-api-key' => MYPLUGIN_GEMINI_KEY,
],
'body' => wp_json_encode( [
'contents' => [ [ 'parts' => [ [ 'text' => $prompt ] ] ] ],
] ),
]
);
if ( is_wp_error( $response ) ) {
return $response;
}
$code = wp_remote_retrieve_response_code( $response );
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 200 !== $code ) {
$message = $data['error']['message'] ?? 'Unknown Gemini API error.';
return new WP_Error( 'gemini_error', $message, [ 'status' => $code ] );
}
$text = '';
foreach ( $data['candidates'][0]['content']['parts'] ?? [] as $part ) {
if ( empty( $part['thought'] ) && isset( $part['text'] ) ) {
$text .= $part['text'];
}
}
return $text;
}
Define MYPLUGIN_GEMINI_KEY in wp-config.php, above the “That’s all, stop editing” line. Keys in the database (options table) are one SQL injection away from leaking; keys in config files are at least out of the database backup chain.
Worth knowing: WordPress 7.0 now ships a provider-agnostic AI client in core, which changes how plugins should consume AI; I cover that in the next post in this series. The raw API knowledge here still matters: it’s what the abstractions are built on, and it’s what you’ll use outside WordPress.
Tuning output with generationConfig
Add a generationConfig object alongside contents to control the output:
$body = [
'contents' => [ [ 'parts' => [ [ 'text' => $prompt ] ] ] ],
'systemInstruction' => [
'parts' => [ [ 'text' => 'You are a concise technical writer. Plain language, no fluff.' ] ],
],
'generationConfig' => [
'maxOutputTokens' => 2048,
'thinkingConfig' => [ 'thinkingLevel' => 'LOW' ],
],
];
systemInstructionis where persistent behavior belongs: tone, format, role. Don’t repeat it in every user prompt.thinkingLeveltrades reasoning depth for speed and cost. Gemini 3.8 Flash thinks by default (medium) and accepts low, medium or high;LOWis plenty for summaries, tags and alt text. Older 2.5 models reject it and usethinkingBudgetinstead.- Leave
temperatureout. The API reference says it is silently ignored on Gemini 3.6 Flash and newer, and Google’s Gemini 3 guide advises keeping the default of 1.0 on Gemini 3 models, since lower values can cause looping. This post originally set 0.3 for factual tasks; on current models that does nothing. maxOutputTokensis a cost seatbelt, not a formatting tool, and on thinking models it includes the thought tokens. Set it too low and the answer comes back cut off or empty withfinishReasonset toMAX_TOKENS, and you still pay for the thinking. If you need short output, ask for short output in the prompt.
Errors you will actually hit
Three failures account for nearly everything in production:
- 429 rate limits: you exceeded requests-per-minute quota. Back off exponentially and retry. On the free tier this happens fast in any loop.
- Empty candidates: the response can come back with no usable part. If the prompt itself was blocked,
promptFeedback.blockReasonis set and there are no candidates; if the answer was stopped, the candidate’sfinishReasonsays why (SAFETY,MAX_TOKENSand others;STOPis the normal one). That’s why the code above falls back to an empty array: never assume the text path exists, and logfinishReasonwhen you get an empty string. - Timeouts: generation can take longer than PHP’s default HTTP timeouts. Set 60 seconds, and for anything user-facing, move calls into a background job rather than blocking a page load.
Cost control from day one
Two habits, both cheap to adopt now and painful to retrofit:
- Cache by content hash. If you’re generating something derived from content (a summary, alt text, tags), key the cache on
md5( $model . $prompt ), and only cache a result whosefinishReasonisSTOPso a truncated answer doesn’t stick. Regenerating identical output is pure waste. In WordPress, transients make this a three-line addition. - Default to Flash.
gemini-3.8-flashis fast, has a free tier and handles most content tasks well (for the cheapest bulk work, Google also listsgemini-3.5-flash-lite); reserve Pro-class models for tasks that demonstrably need deeper reasoning. Model names and pricing evolve, so check Google’s current model list rather than hardcoding assumptions, another reason to keep the model name in one configurable place.
Continue the Gemini and WordPress series: build a WordPress plugin with Gemini, read the complete Gemini and WordPress developer guide, or learn how to secure Gemini API keys in WordPress.
Frequently asked questions
Do I need a PHP SDK to call the Gemini API?
No. A server-side HTTPS request to the generateContent endpoint is enough. An SDK can add convenience, but plain cURL or the WordPress HTTP API makes the request, timeout, error handling and key usage visible.
Where should a WordPress site store its Gemini API key?
Keep the key outside post content and the database when possible. Define it in wp-config.php or inject it as an environment-backed constant, never commit it to a plugin, theme or public repository.
Which Gemini model should a PHP integration use?
Start with a current Flash model for ordinary text generation because it is fast and economical: as of October 2026 that is gemini-3.8-flash, since Gemini 2.5 models are only available to projects that already used them. Keep the model in one configurable value and check Google’s models page before deployment because model names, limits and pricing change.
How should PHP handle Gemini API failures?
Check transport errors, the HTTP status and whether candidates contain usable text. Retry rate limits with backoff, set an explicit timeout, and do not display raw provider responses or API keys to visitors.
Where to go next
You now have the foundation the rest of this series builds on: the parts-in/parts-out request shape, safe key handling, and honest error handling. Next up: WordPress 7.0’s native AI layer, covering what core now gives you for free, and how it changes what a well-behaved AI plugin looks like. Before you ship any of it, read how to detect and contain AI failures in a WordPress plugin: the structure checks, bounded retries and test matrix that sit around the call you just wrote.