How to Use OpenAI API with PHP (Complete Guide, 2026)

PHP powers a large part of the web, but most AI tutorials focus on Python. This guide fills that gap: a complete OpenAI PHP tutorial covering installation, text generation, chat completions, streaming, and function calling — all with working code you can drop into any PHP project, Laravel or plain PHP.


Prerequisites


Installation

The official openai-php/client library is the standard choice:

composer require openai-php/client

Store your API key in .env (never hardcode it):

OPENAI_API_KEY=sk-proj-...

Basic Setup

<?php

require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$client = OpenAI::client($_ENV['OPENAI_API_KEY']);

If you’re not using a .env loader, you can also pass the key directly (fine for local dev):

$client = OpenAI::client(getenv('OPENAI_API_KEY'));

Text Generation (Chat Completions)

The chat.completions.create endpoint is the main interface for GPT-4o and GPT-4o-mini:

<?php

$response = $client->chat()->create([
    'model' => 'gpt-4o',
    'messages' => [
        ['role' => 'system', 'content' => 'You are a helpful PHP developer assistant.'],
        ['role' => 'user', 'content' => 'Explain what a PHP trait is in two sentences.'],
    ],
    'max_tokens' => 256,
]);

echo $response->choices[0]->message->content;
// Output: A trait in PHP is a mechanism for code reuse...

echo "\nTokens used: " . $response->usage->totalTokens;

Multi-Turn Conversations

To build a conversation with memory, maintain the messages array across turns:

<?php

$messages = [
    ['role' => 'system', 'content' => 'You are a helpful assistant.'],
];

function chat(array &$messages, string $userMessage, $client): string
{
    $messages[] = ['role' => 'user', 'content' => $userMessage];

    $response = $client->chat()->create([
        'model' => 'gpt-4o-mini',
        'messages' => $messages,
        'max_tokens' => 512,
    ]);

    $reply = $response->choices[0]->message->content;
    $messages[] = ['role' => 'assistant', 'content' => $reply];

    return $reply;
}

echo chat($messages, 'What is a closure in PHP?', $client) . "\n";
echo chat($messages, 'Give me a code example.', $client) . "\n";

Streaming Responses

Streaming lets you display the response token by token as it arrives — essential for chatbots and user-facing apps:

<?php

// Set headers for Server-Sent Events
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');

$stream = $client->chat()->createStreamed([
    'model' => 'gpt-4o',
    'messages' => [
        ['role' => 'user', 'content' => 'Explain dependency injection in PHP.'],
    ],
    'max_tokens' => 512,
]);

foreach ($stream as $response) {
    $text = $response->choices[0]->delta->content ?? '';
    if ($text !== '') {
        echo "data: " . json_encode(['text' => $text]) . "\n\n";
        ob_flush();
        flush();
    }
}

echo "data: [DONE]\n\n";

On the frontend, consume the SSE stream with EventSource or fetch with a reader:

const eventSource = new EventSource('/api/stream');

eventSource.onmessage = (e) => {
  if (e.data === '[DONE]') {
    eventSource.close();
    return;
  }
  const { text } = JSON.parse(e.data);
  document.getElementById('output').textContent += text;
};

Function Calling (Tool Use)

Function calling lets the model invoke your PHP functions when it decides it needs external data:

<?php

$tools = [
    [
        'type' => 'function',
        'function' => [
            'name' => 'get_product_price',
            'description' => 'Get the current price of a product by SKU',
            'parameters' => [
                'type' => 'object',
                'properties' => [
                    'sku' => [
                        'type' => 'string',
                        'description' => 'Product SKU code',
                    ],
                ],
                'required' => ['sku'],
            ],
        ],
    ],
];

$messages = [
    ['role' => 'user', 'content' => 'What is the price of product SKU-1234?'],
];

$response = $client->chat()->create([
    'model' => 'gpt-4o',
    'messages' => $messages,
    'tools' => $tools,
    'tool_choice' => 'auto',
]);

$choice = $response->choices[0];

if ($choice->finishReason === 'tool_calls') {
    $toolCall = $choice->message->toolCalls[0];
    $args = json_decode($toolCall->function->arguments, true);

    // Execute your actual function
    $price = getProductPrice($args['sku']);

    // Add tool result to messages and call again
    $messages[] = ['role' => 'assistant', 'content' => null, 'tool_calls' => $response->choices[0]->message->toolCalls];
    $messages[] = [
        'role' => 'tool',
        'tool_call_id' => $toolCall->id,
        'content' => (string) $price,
    ];

    $finalResponse = $client->chat()->create([
        'model' => 'gpt-4o',
        'messages' => $messages,
    ]);

    echo $finalResponse->choices[0]->message->content;
}

function getProductPrice(string $sku): float
{
    // Query your database
    return 29.99; // example
}

Image Analysis (Vision)

GPT-4o supports image inputs alongside text:

<?php

// Pass image as URL
$response = $client->chat()->create([
    'model' => 'gpt-4o',
    'messages' => [
        [
            'role' => 'user',
            'content' => [
                ['type' => 'text', 'text' => 'What does this error message say?'],
                [
                    'type' => 'image_url',
                    'image_url' => ['url' => 'https://example.com/screenshot.png'],
                ],
            ],
        ],
    ],
    'max_tokens' => 512,
]);

echo $response->choices[0]->message->content;

// Pass image as base64
$imageData = base64_encode(file_get_contents('/path/to/image.png'));
$response = $client->chat()->create([
    'model' => 'gpt-4o',
    'messages' => [
        [
            'role' => 'user',
            'content' => [
                ['type' => 'text', 'text' => 'Describe this image.'],
                [
                    'type' => 'image_url',
                    'image_url' => ['url' => "data:image/png;base64,{$imageData}"],
                ],
            ],
        ],
    ],
]);

Laravel Integration

In a Laravel application, register the client as a singleton in a service provider:

<?php
// app/Providers/OpenAIServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use OpenAI;
use OpenAI\Client;

class OpenAIServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(Client::class, function () {
            return OpenAI::client(config('services.openai.key'));
        });
    }
}
# config/services.php
'openai' => [
    'key' => env('OPENAI_API_KEY'),
],
<?php
// app/Http/Controllers/ChatController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use OpenAI\Client;

class ChatController extends Controller
{
    public function __construct(private readonly Client $openai) {}

    public function ask(Request $request): string
    {
        $response = $this->openai->chat()->create([
            'model' => 'gpt-4o-mini',
            'messages' => [
                ['role' => 'user', 'content' => $request->input('message')],
            ],
            'max_tokens' => 512,
        ]);

        return $response->choices[0]->message->content;
    }
}

Error Handling

<?php

use OpenAI\Exceptions\UnserializableResponse;
use OpenAI\Exceptions\TransporterException;

try {
    $response = $client->chat()->create([
        'model' => 'gpt-4o',
        'messages' => [['role' => 'user', 'content' => $userMessage]],
        'max_tokens' => 512,
    ]);

    return $response->choices[0]->message->content;

} catch (\Exception $e) {
    // Check for rate limit (HTTP 429)
    if (str_contains($e->getMessage(), '429') || str_contains($e->getMessage(), 'rate_limit')) {
        sleep(2);
        // retry logic here
    }

    // Check for invalid API key (HTTP 401)
    if (str_contains($e->getMessage(), '401') || str_contains($e->getMessage(), 'invalid_api_key')) {
        throw new \RuntimeException('Invalid OpenAI API key');
    }

    throw $e;
}

Cost Control

  • Use gpt-4o-mini for simple tasks (classification, extraction) — it’s 17x cheaper than gpt-4o
  • Set max_tokens explicitly — never let the model return unlimited output
  • Cache responses in Redis for identical or near-identical prompts
  • Log token usage per request to track costs early
<?php

// Log token usage for cost tracking
$response = $client->chat()->create([...]);

$usage = $response->usage;
\Log::info('OpenAI usage', [
    'prompt_tokens'     => $usage->promptTokens,
    'completion_tokens' => $usage->completionTokens,
    'total_tokens'      => $usage->totalTokens,
    // gpt-4o pricing: $2.50/1M input, $10/1M output
    'estimated_cost_usd' => ($usage->promptTokens / 1_000_000 * 2.50)
                          + ($usage->completionTokens / 1_000_000 * 10),
]);

Summary

The openai-php/client library makes it straightforward to add GPT-4o to any PHP application. The key patterns: use chat().create for standard completions, add SSE streaming for user-facing chatbots, function calling for tool use, and register a singleton in Laravel for clean dependency injection.

For working with Claude API in PHP instead, the pattern is similar — see the Claude API docs for the PHP SDK options.


Subscribe to my newsletter — practical guides on Claude API, AI agents, RAG, and automation.

Subscribe