How to Build an AI Chatbot with PHP (Step-by-Step, 2026)
This guide builds a working web AI chatbot with PHP: a backend that calls OpenAI, session-based conversation history, streaming SSE, and a clean HTML/JS frontend.
Requirements
- PHP 8.1+, Composer, OpenAI API key
Install
composer require openai-php/client
chat.php — Non-Streaming Backend
<?php
require 'vendor/autoload.php';
session_start();
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true)['message'] ?? '';
if (!isset($_SESSION['history'])) {
$_SESSION['history'] = [['role' => 'system', 'content' => 'You are a helpful assistant.']];
}
$_SESSION['history'][] = ['role' => 'user', 'content' => $input];
$client = OpenAI::client(getenv('OPENAI_API_KEY'));
$response = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => array_slice($_SESSION['history'], -20),
]);
$reply = $response->choices[0]->message->content;
$_SESSION['history'][] = ['role' => 'assistant', 'content' => $reply];
echo json_encode(['reply' => $reply]);
stream.php — Streaming SSE Backend
<?php
require 'vendor/autoload.php';
session_start();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$input = json_decode(file_get_contents('php://input'), true)['message'] ?? '';
$_SESSION['history'][] = ['role' => 'user', 'content' => $input];
$stream = OpenAI::client(getenv('OPENAI_API_KEY'))->chat()->createStreamed([
'model' => 'gpt-4o-mini',
'messages' => $_SESSION['history'],
]);
$full = '';
foreach ($stream as $response) {
$delta = $response->choices[0]->delta->content;
if ($delta !== null) {
$full .= $delta;
echo "data: " . json_encode(['chunk' => $delta]) . "\n\n";
ob_flush(); flush();
}
}
$_SESSION['history'][] = ['role' => 'assistant', 'content' => $full];
echo "data: [DONE]\n\n";
Frontend (EventSource / fetch streaming)
const res = await fetch('stream.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
for (const line of buffer.split('\n')) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6);
if (data === '[DONE]') return;
const { chunk } = JSON.parse(data);
outputEl.textContent += chunk;
}
}
Production Checklist
- Set
OPENAI_API_KEYas server env var (never hardcode) - Use HTTPS for SSE + sessions
- Trim
$_SESSION['history']to avoid token overflow - Add per-user rate limiting to control costs
- Catch
ErrorException+TransporterException
Originally published at kalyna.pro