baconfy/prompt

Manage AI prompts in Laravel with Markdown + YAML front matter, multiple drivers (file/database), and Blade rendering.

Maintainers

Package info

github.com/baconfy/prompt

pkg:composer/baconfy/prompt

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.2 2026-05-08 22:33 UTC

This package is auto-updated.

Last update: 2026-05-09 19:06:01 UTC


README

Prompt

Tests Latest Version License Total Downloads PHP Version

Manage AI prompts in Laravel as Markdown files (with optional YAML front matter) or database records, rendered through Blade.

Why

LLM prompts are configuration. They drift across the codebase, get duplicated, and end up hardcoded as long strings inside services. This package treats prompts as first-class assets:

  • One file or DB record per prompt
  • YAML front matter for model/temperature/required variables
  • Blade rendering for variable interpolation
  • Drivers for file and database storage
  • Strict validation of required variables

Requirements

  • PHP 8.3+
  • Laravel 12+

Installation

composer require baconfy/prompt

The service provider auto-registers. Publish what you need:

php artisan vendor:publish --tag=prompt-config
php artisan vendor:publish --tag=prompt-migrations    # only if you plan to use the database driver
php artisan migrate

Quick start

Create a prompt at resources/prompts/welcome.md:

---
model: claude-opus-4-5
temperature: 0.7
required: [name]
---
You are a helpful assistant. Greet {{ $name }} warmly and ask how you can help today.

Render it:

$prompt = prompt('welcome', ['name' => 'John']);

$prompt->content;           // rendered string
$prompt->metadata['model']; // 'claude-opus-4-5'
(string) $prompt;           // same as ->content (implements Stringable)

Use the metadata to drive your LLM call:

$prompt = prompt('welcome', ['name' => 'John']);

$response = $anthropic->messages()->create([
    'model'       => $prompt->metadata['model'],
    'temperature' => $prompt->metadata['temperature'],
    'messages'    => [['role' => 'user', 'content' => (string) $prompt]],
]);

Front matter

Front matter is an optional YAML block at the top of the prompt:

---
model: claude-opus-4-5
temperature: 0.7
required: [user_name, context]
description: Onboarding greeting
tags: [onboarding, greeting]
---
Hello {{ $user_name }}! Considering {{ $context }}, welcome aboard.

Behavior:

  • If the file does not start with ---, it is treated as plain content (no metadata, no validation, just Blade).
  • required: [...] is enforced. Missing variables throw MissingRequiredVariablesException.
  • Anything else is metadata. The package does not interpret it; read it via $prompt->metadata['anything'].

Drivers

File driver

Default. Reads from resources/prompts/*.md. Dot notation maps to subfolders:

prompt('auth.login');               // resources/prompts/auth/login.md
prompt('emails.welcome.subject');   // resources/prompts/emails/welcome/subject.md

Database driver

Stores prompts in a prompts table with name and content columns. The content column holds raw markdown — exactly the same format as the file driver. Front matter, when present, sits inline at the top of content.

use Baconfy\Prompt\Models\Prompt;

Prompt::create([
    'name'    => 'welcome',
    'content' => <<<'MD'
        ---
        model: claude-opus-4-5
        required: [name]
        ---
        Hello {{ $name }}!
        MD,
]);

prompt('welcome', ['name' => 'John']);   // works the same way

Migrating prompts between file and database drivers is a copy/paste — the storage format is identical.

Switch the default driver in .env:

PROMPTS_DRIVER=database
PROMPTS_CONNECTION=mysql       # optional, falls back to DB_CONNECTION
PROMPTS_TABLE=prompts          # optional

Or use both side by side:

// config/prompt.php
'drivers' => [
    'system' => [
        'driver' => 'file',
        'folder' => resource_path('prompts/system'),
    ],
    'user' => [
        'driver' => 'database',
        'table'  => 'user_prompts',
    ],
],
Prompt::driver('system')->find('welcome');
Prompt::driver('user')->find('welcome');

API

Helper

prompt(string $name, array $data = []): RenderedPrompt

Facade

use Baconfy\Prompt\Facades\Prompt;

Prompt::get('welcome', ['name' => 'John']);   // RenderedPrompt
Prompt::source('welcome');                     // ParsedFrontMatter|null
Prompt::driver('database');                    // Driver instance (defaults to active)

RenderedPrompt

$prompt->content;    // rendered string
$prompt->metadata;   // array<string, mixed>
(string) $prompt;    // same as ->content

ParsedFrontMatter

What Prompt::source() returns — pre-render. Useful when you want metadata without rendering Blade:

$source = Prompt::source('welcome');
$source->metadata['model'];   // 'claude-opus-4-5'
$source->content;             // raw template, with Blade tags untouched

Prompt model

Eloquent model on the prompts table. Use it to seed, update, or otherwise manage DB-backed prompts:

use Baconfy\Prompt\Models\Prompt as PromptModel;

PromptModel::create([
    'name'    => 'welcome',
    'content' => <<<'MD'
        ---
        model: claude-opus-4-5
        ---
        Hello {{ $name }}!
        MD,
]);

PromptModel::where('name', 'welcome')->update(['content' => 'Hi {{ $name }}!']);

The driver itself does not depend on this model — it reads via Query Builder. The model is a convenience for your CRUD layer.

CLI

Three Artisan commands ship with the package:

php artisan prompt:list                              # list prompts across all configured drivers
php artisan prompt:list database                     # list prompts for a specific driver
php artisan prompt:show welcome                      # show metadata and raw content for a single prompt
php artisan prompt:show welcome --driver=database    # target a specific driver
php artisan prompt:make welcome                      # scaffold resources/prompts/welcome.md

prompt:list accepts an optional driver argument (the name of any driver defined in config/prompt.php); omitting it iterates all drivers. prompt:show accepts a --driver= option to target a specific named driver instead of the active default. prompt:make is file-driver only — database prompts are created directly via the prompts table.

prompt:show is your debug companion: confirm what Prompt::source('welcome') will return before rendering anywhere.

Testing

Faking prompts

In your application's tests, replace the real driver with a stub so prompts don't hit the filesystem or database:

use Baconfy\Prompt\Facades\Prompt;
use Baconfy\Prompt\RenderedPrompt;

Prompt::fake([
    'welcome' => 'Hello stub!',
    'auth.login' => new RenderedPrompt('Stub login.', ['model' => 'gpt-4']),
]);

// code under test
prompt('welcome', ['name' => 'whatever']);

Prompt::assertCalled('welcome');
Prompt::assertNotCalled('checkout');

A plain string stub is wrapped in a RenderedPrompt with empty metadata; pass a RenderedPrompt instance when your code reads $prompt->metadata.

Factories

The Prompt model ships with an Eloquent factory for seeding test data when you use the database driver:

use Baconfy\Prompt\Models\Prompt;

Prompt::factory()->create();
Prompt::factory()->count(5)->create();
Prompt::factory()->create(['name' => 'welcome', 'content' => 'Hi!']);

Configuration

config/prompt.php:

return [
    'default' => env('PROMPTS_DRIVER', 'file'),

    'drivers' => [
        'file' => [
            'driver' => 'file',
            'folder' => env('PROMPTS_FOLDER', resource_path('prompts')),
        ],
        'database' => [
            'driver'     => 'database',
            'connection' => env('PROMPTS_CONNECTION'),
            'table'      => env('PROMPTS_TABLE', 'prompts'),
        ],
    ],
];

The drivers array supports any number of named entries. Each one has a driver field (file or database) plus the keys that driver needs. The same type can appear under multiple names (e.g. two file folders for system vs. user prompts).

Exceptions

use Baconfy\Prompt\Exceptions\PromptNotFoundException;
use Baconfy\Prompt\Exceptions\MissingRequiredVariablesException;
  • PromptNotFoundException — thrown by Prompt::get() when the name is not found by the active driver. Exposes ->name.
  • MissingRequiredVariablesException — thrown when the prompt declares required in its metadata and any variable is missing from $data. Exposes ->variables (the list of missing names).

Security

Blade compiles prompt content. Do not load prompt content from untrusted sources. A prompt containing {{ system('rm -rf /') }} would execute that PHP if rendered. Treat prompts as code, not user input.

Development

Run the package test suite:

composer test            # pest
composer test:coverage   # 100% required
composer test:types      # phpstan
composer format          # pint

Credits

License

Licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later). See LICENSE for details.