Laravel Daily's

Content Generation in Laravel: From Prompt to Published Post with the AI SDK

hero image

AI content generation works best when it fits the application around it.

The model should not decide how your database works. Your Laravel application should define the output shape, queue slow work, handle failures, and keep a human review step before publication.

In this tutorial, you will build a content generation workflow with the Laravel AI SDK. A topic prompt becomes a structured draft with a title, excerpt, sections, and tags. The result is saved to the database.

You will also see:

  • How AI::text() fits into a Laravel workflow
  • How to install and configure laravel/ai
  • How to define system instructions with an agent
  • How to enforce structured output
  • How to queue generation with then() and catch()
  • How to configure OpenAI and Anthropic failover
  • How Laravel Boost can support the developer workflow

The workflow: Prompt, generate, review, publish

A production content feature needs more than a model call.

The workflow should look like this:

  1. A user submits a topic.
  2. Laravel creates a draft record with a generating status.
  3. The AI SDK generates structured content in the background.
  4. A queue callback saves the result.
  5. An editor reviews the draft.
  6. Laravel publishes the post.

This approach keeps the HTTP request fast. It also gives your application a clear state for each generation.

Illustration of Laravel AI SDK transforming a prompt into JSON content fields

Install the Laravel AI SDK

Install the package with Composer:

composer require laravel/ai

Publish the SDK configuration and migration files:

php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate

The published configuration lives at config/ai.php. The migrations support the SDK’s conversation features. You can keep them even if this feature does not use conversation memory.

Add your provider keys to .env:

OPENAI_API_KEY=your-openai-api-key
ANTHROPIC_API_KEY=your-anthropic-api-key

The default provider is configured in config/ai.php:

'default' => 'openai',

Keep keys in environment variables. Do not commit them to source control.

Start with AI::text() for simple generation

The SDK provides a unified interface for text generation. For a one-off, free-form response, you can use the AI::text() style API exposed by your installed SDK version or application wrapper:

use Laravel\Ai\Facades\Ai as AI;

$response = AI::text(
    'Write a short introduction to Laravel queues for PHP developers.'
);

$text = $response->text();

This is useful for simple text tasks. It is not enough for a database-backed content workflow.

A content pipeline needs reusable instructions and a predictable response shape. The Laravel AI SDK handles those concerns through agents.

Use an agent when the operation has a defined responsibility. The agent can hold the system instructions, schema, provider settings, and test behavior in one PHP class.

Create a structured content agent

Generate an agent with the structured output option:

php artisan make:agent BlogPostWriter --structured

The generated class belongs in app/Ai/Agents/BlogPostWriter.php.

Define its instructions and schema:

<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class BlogPostWriter implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string
    {
        return <<<'PROMPT'
You are an experienced technical editor.

Create a useful first draft for PHP developers.
Use a clear and professional tone.
Prefer short declarative sentences.
Do not invent benchmarks, product features, or API behavior.
Return only the fields defined by the schema.
Write section bodies in Markdown.
PROMPT;
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'title' => $schema->string()->required(),

            'excerpt' => $schema->string()->required(),

            'sections' => $schema->array()->items(
                $schema->object(fn (JsonSchema $schema) => [
                    'heading' => $schema->string()->required(),
                    'body' => $schema->string()->required(),
                ])
            )->required(),

            'tags' => $schema->array()->items(
                $schema->string()
            )->required(),
        ];
    }
}

The instructions() method acts as the agent’s system prompt. It defines the role and the rules that should remain consistent across requests.

The schema() method defines the contract between the model and your application.

The response is structured. You can access its fields like an array:

$response = (new BlogPostWriter)->prompt(
    'Create a blog post about building a REST API with PHP and Laravel.'
);

$title = $response['title'];
$excerpt = $response['excerpt'];
$sections = $response['sections'];
$tags = $response['tags'];

This is safer than asking for JSON in a plain text prompt and parsing whatever the model returns.

Recipe: create the post record first

Your database should track the generation state.

A minimal posts table might include:

$table->id();
$table->string('title')->nullable();
$table->string('slug')->nullable()->unique();
$table->text('excerpt')->nullable();
$table->longText('content')->nullable();
$table->json('tags')->nullable();
$table->string('status')->default('generating');
$table->timestamp('published_at')->nullable();
$table->timestamps();

Create a draft before calling the model:

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

public function store(Request $request)
{
    $validated = $request->validate([
        'topic' => ['required', 'string', 'max:500'],
    ]);

    $post = Post::create([
        'status' => 'generating',
        'title' => 'Generating draft...',
        'content' => null,
        'tags' => [],
    ]);

    $this->generateDraft($post, $validated['topic']);

    return response()->json([
        'id' => $post->id,
        'status' => $post->status,
    ], 202);
}

The 202 Accepted response tells the client that the request was accepted for background processing.

Queue generation with then() and catch()

AI requests can take longer than normal web requests. Queue them instead of keeping the browser waiting.

use App\Ai\Agents\BlogPostWriter;
use App\Models\Post;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\Enums\Lab;
use Throwable;

protected function generateDraft(Post $post, string $topic): void
{
    BlogPostWriter::make()
        ->queue(
            <<<PROMPT
Create a structured blog post draft about this topic:

{$topic}

The intended audience is PHP developers.
Include practical Laravel examples where relevant.
PROMPT,
            provider: [Lab::OpenAI, Lab::Anthropic],
        )
        ->then(function ($response) use ($post) {
            $content = collect($response['sections'])
                ->map(fn (array $section) =>
                    "## {$section['heading']}\n\n{$section['body']}"
                )
                ->implode("\n\n");

            $post->update([
                'title' => $response['title'],
                'excerpt' => $response['excerpt'],
                'content' => $content,
                'tags' => $response['tags'],
                'status' => 'draft',
            ]);
        })
        ->catch(function (Throwable $exception) use ($post) {
            Log::error('Blog post generation failed.', [
                'post_id' => $post->id,
                'error' => $exception->getMessage(),
            ]);

            $post->update([
                'status' => 'generation_failed',
            ]);
        });
}

The queue() method dispatches the AI operation through Laravel’s queue system.

The then() callback runs when generation succeeds. The structured response can be transformed into the Markdown stored in content.

The catch() callback runs when the operation fails. That includes a total provider failure or another exception that cannot be recovered automatically.

Run a worker for the queue:

php artisan queue:work --queue=default --timeout=120

For larger applications, use a dedicated queue:

BlogPostWriter::make()
    ->queue($prompt, provider: [Lab::OpenAI, Lab::Anthropic])
    ->onQueue('ai');

Then run an AI-specific worker:

php artisan queue:work --queue=ai --timeout=120

Illustration of Laravel queue workers processing AI content cards with provider failover

Recipe: provider failover

AI providers can rate-limit requests or experience service interruptions. The SDK lets you define a provider sequence for a request.

use Laravel\Ai\Enums\Lab;

$providers = [
    Lab::OpenAI,
    Lab::Anthropic,
];

Pass that array to queue():

BlogPostWriter::make()
    ->queue(
        'Create a structured draft about PHP developer tools.',
        provider: [Lab::OpenAI, Lab::Anthropic],
    )
    ->then(function ($response) {
        // Save the structured response.
    })
    ->catch(function (Throwable $exception) {
        // All configured providers failed.
    });

The SDK tries OpenAI first. If the failure is eligible for failover, it tries Anthropic next.

The same provider list can be used with synchronous agent prompts:

$response = BlogPostWriter::make()->prompt(
    'Create a draft about how to build a REST API with PHP.',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

Provider failover keeps your application code focused on the content workflow. You do not need to write separate HTTP clients or duplicate prompt logic for each provider.

Keep publication separate from generation

Generated content should become a draft, not an automatically published article.

A simple publish action can validate the editorial state:

use Illuminate\Support\Str;

public function publish(Post $post)
{
    abort_unless($post->status === 'draft', 422);

    $post->update([
        'slug' => Str::slug($post->title),
        'status' => 'published',
        'published_at' => now(),
    ]);

    return redirect()->route('posts.show', $post);
}

This separation gives editors a review point. They can correct claims, improve examples, add links, and remove irrelevant sections before publication.

The same pattern works for product descriptions, support articles, release notes, and internal documentation.

Test the workflow without making API calls

The AI SDK includes fakes for agents. Use them in feature tests:

use App\Ai\Agents\BlogPostWriter;
use Illuminate\Support\Facades\Queue;

public function test_it_queues_blog_post_generation(): void
{
    BlogPostWriter::fake();

    $response = $this->postJson('/posts/generate', [
        'topic' => 'PHP developer tools for modern Laravel applications',
    ]);

    $response
        ->assertAccepted()
        ->assertJsonPath('status', 'generating');

    BlogPostWriter::assertQueued(function ($prompt) {
        return $prompt->contains('PHP developer tools');
    });
}

This test checks that your application queues the correct work. It does not spend tokens or depend on an external provider.

For a full integration test, run the queue with a fake response and assert that the post changes from generating to draft.

Use Laravel Boost during development

Laravel Boost can support AI-assisted development inside a Laravel project. It gives coding agents project-specific context, Laravel guidance, and access to skills designed for the framework.

That can help when you are:

  • Creating the agent class
  • Inspecting your Laravel version
  • Writing migrations and tests
  • Reviewing queue behavior
  • Finding the correct SDK APIs

Boost does not replace application tests or editorial review. It helps shorten the path from an idea to a working Laravel implementation.

From prompt to published post

The important design decision is not the model. It is the boundary around the model.

Use AI::text() for small, free-form tasks. Use an agent when you need reusable instructions and structured output. Use queues for slow work. Use provider failover for resilience. Save generated content as a draft before anyone publishes it.

That combination gives a PHP developer a maintainable foundation for AI features. The prompt starts the workflow, but Laravel owns everything that follows.

Read the Laravel AI SDK documentation, explore the AI SDK source on GitHub, and adapt this recipe to the content workflow your application already needs.

Previous
Agent Run Observability in Laravel AI SDK 0.11: Trace Every Step, Tool Call, and Failure
Next
Type-Safe Laravel + Vue + Inertia: From PHP DTOs to Typed Vue Props