Laravel is the most popular PHP framework, and adding OpenAI to it is straightforward with the official openai-php/client library. This guide walks through a complete integration: installing the SDK, creating a service class, building a chat controller, streaming responses, and handling errors. By the end you’ll have a working AI chat feature in your Laravel app.
For a lower-level look at the PHP SDK itself, see OpenAI PHP SDK Guide. For using OpenAI without Laravel, see OpenAI PHP Tutorial.
Requirements
- Laravel 10 or 11
- PHP 8.1+
- Composer
- OpenAI API key
Step 1: Install the OpenAI PHP Client
Install the official OpenAI PHP SDK via Composer:
composer require openai-php/laravelThen publish the config file:
php artisan vendor:publish --provider="OpenAI\\Laravel\\ServiceProvider"This creates config/openai.php which reads the API key from your environment.
Step 2: Configure the API Key
Add your OpenAI API key to .env:
OPENAI_API_KEY=sk-proj-...
OPENAI_ORGANIZATION= # optionalStep 3: Create the OpenAI Service
Create app/Services/OpenAIService.php:
<?php
namespace App\Services;
use OpenAI\Laravel\Facades\OpenAI;
class OpenAIService
{
public function chat(array $messages, string $model = 'gpt-4o-mini'): string
{
$response = OpenAI::chat()->create([
'model' => $model,
'messages' => $messages,
'max_tokens' => 1024,
'temperature' => 0.7,
]);
return $response->choices[0]->message->content;
}
public function stream(array $messages, string $model = 'gpt-4o-mini'): \Generator
{
$stream = OpenAI::chat()->createStreamed([
'model' => $model,
'messages' => $messages,
]);
foreach ($stream as $response) {
$delta = $response->choices[0]->delta->content;
if ($delta !== null) {
yield $delta;
}
}
}
}Step 4: Create the Chat Controller
php artisan make:controller ChatControllerEdit app/Http/Controllers/ChatController.php:
<?php
namespace App\Http\Controllers;
use App\Services\OpenAIService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class ChatController extends Controller
{
public function __construct(private OpenAIService $openAI) {}
public function index()
{
return view('chat.index');
}
public function send(Request $request): JsonResponse
{
$request->validate([
'message' => 'required|string|max:2000',
'history' => 'array',
]);
$messages = $request->input('history', []);
$messages[] = ['role' => 'user', 'content' => $request->input('message')];
$reply = $this->openAI->chat($messages);
return response()->json([
'reply' => $reply,
'messages' => array_merge($messages, [
['role' => 'assistant', 'content' => $reply],
]),
]);
}
public function stream(Request $request)
{
$request->validate(['message' => 'required|string|max:2000']);
$messages = [
['role' => 'system', 'content' => 'You are a helpful assistant.'],
['role' => 'user', 'content' => $request->input('message')],
];
return response()->stream(function () use ($messages) {
foreach ($this->openAI->stream($messages) as $chunk) {
echo "data: " . json_encode(['chunk' => $chunk]) . "\n\n";
ob_flush();
flush();
}
echo "data: [DONE]\n\n";
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no',
]);
}
}Step 5: Register Routes
Add to routes/web.php:
use App\Http\Controllers\ChatController;
Route::get('/chat', [ChatController::class, 'index'])->name('chat.index');
Route::post('/chat/send', [ChatController::class, 'send'])->name('chat.send');
Route::post('/chat/stream', [ChatController::class, 'stream'])->name('chat.stream');Step 6: Create the Blade View
Create resources/views/chat/index.blade.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>AI Chat</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
#messages { height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 16px;
border-radius: 8px; margin-bottom: 16px; background: #f9f9f9; }
.user { color: #1a56db; font-weight: 600; margin-bottom: 8px; }
.assistant { color: #111; margin-bottom: 8px; }
#form { display: flex; gap: 8px; }
#input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 6px; }
button { padding: 10px 20px; background: #1a56db; color: #fff; border: none;
border-radius: 6px; cursor: pointer; }
</style>
</head>
<body>
<h1>AI Chat</h1>
<div id="messages"></div>
<form id="form">
<input id="input" type="text" placeholder="Ask something..." autocomplete="off">
<button type="submit">Send</button>
</form>
<script>
const msgs = document.getElementById('messages');
const input = document.getElementById('input');
let history = [];
function add(role, text) {
const d = document.createElement('div');
d.className = role;
d.innerHTML = '<strong>' + (role === 'user' ? 'You' : 'AI') + ':</strong> ' + text;
msgs.appendChild(d);
msgs.scrollTop = msgs.scrollHeight;
}
document.getElementById('form').addEventListener('submit', async (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
input.value = '';
add('user', text);
const res = await fetch('/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
},
body: JSON.stringify({ message: text, history }),
});
const data = await res.json();
add('assistant', data.reply);
history = data.messages;
});
</script>
</body>
</html>Error Handling
Wrap OpenAI calls to handle API errors and network failures:
use OpenAI\Exceptions\ErrorException;
use OpenAI\Exceptions\TransporterException;
try {
$reply = $this->openAI->chat($messages);
} catch (ErrorException $e) {
// API error: invalid key, quota exceeded, invalid request
Log::error('OpenAI API error', ['message' => $e->getMessage()]);
return response()->json(['error' => 'AI service error. Please try again.'], 503);
} catch (TransporterException $e) {
// Network timeout or connection error
Log::error('OpenAI transport error', ['message' => $e->getMessage()]);
return response()->json(['error' => 'Connection failed. Please try again.'], 503);
}Caching Responses
Cache identical prompts to reduce API costs:
use Illuminate\Support\Facades\Cache;
public function chatCached(string $userMessage): string
{
$key = 'openai_' . md5($userMessage);
return Cache::remember($key, 3600, function () use ($userMessage) {
return $this->chat([['role' => 'user', 'content' => $userMessage]]);
});
}Testing with Fakes
The package ships a OpenAI::fake() helper so you never hit the real API in tests:
use OpenAI\Laravel\Facades\OpenAI;
use OpenAI\Responses\Chat\CreateResponse;
public function test_chat_returns_reply(): void
{
OpenAI::fake([
CreateResponse::fake([
'choices' => [
['message' => ['content' => 'Hello from AI!']],
],
]),
]);
$service = app(OpenAIService::class);
$reply = $service->chat([['role' => 'user', 'content' => 'Hi']]);
$this->assertEquals('Hello from AI!', $reply);
}Summary
- Install via
composer require openai-php/laraveland setOPENAI_API_KEYin.env - Wrap API calls in a service class — keeps controllers clean and testable
- Use
response()->stream()with SSE for real-time streaming output - Handle
ErrorExceptionandTransporterExceptionfor robust error handling - Use
OpenAI::fake()for unit testing without real API calls
For more PHP + AI content, see How to Use OpenAI API with PHP and How to Build an AI Chatbot with PHP.
Subscribe to my newsletter — practical guides on Claude API, AI agents, RAG, and automation.