Skip to content

How to Integrate OpenAI API with Laravel (Complete Guide 2026)

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/laravel

Then 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=  # optional

Step 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 ChatController

Edit 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:

AI Chat
” style=”color:#E6E6E6;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
<!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/laravel and set OPENAI_API_KEY in .env
  • Wrap API calls in a service class — keeps controllers clean and testable
  • Use response()->stream() with SSE for real-time streaming output
  • Handle ErrorException and TransporterException for 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.

Subscribe