Skip to main content

Introduction

Prompt Deck provides a clean, expressive API for loading, rendering, and managing versioned AI prompts in your Laravel application. Prompts are stored as plain files on disk and organised by name, version, and role. They are accessed through the PromptDeck facade, which feels like any other first-party Laravel service. A prompt can contain multiple roles (system, user, assistant, developer, tool, or any custom role you define). Each role’s content supports {{ $variable }} interpolation, and the entire prompt can be converted into a messages array ready to send to OpenAI, Anthropic, or any chat-completion API.
use Veeqtoh\PromptDeck\Facades\PromptDeck;

$prompt = PromptDeck::get('order-summary');

$prompt->system(['tone' => 'friendly']);
// "You are a friendly AI assistant..."

$prompt->user(['input' => $request->message]);
// "Summarise the following order: ..."

Retrieving prompts

The PromptDeck facade

The PromptDeck facade is the primary entry point for loading prompts. It delegates to the PromptManager singleton registered by the service provider:
use Veeqtoh\PromptDeck\Facades\PromptDeck;

// Load the active version
$prompt = PromptDeck::get('order-summary');

// Load a specific version
$prompt = PromptDeck::get('order-summary', 2);
The get method returns a PromptTemplate instance. If no version is specified, the active version is resolved automatically.

Dependency injection

You can also inject the PromptManager directly via Laravel’s service container:
use Veeqtoh\PromptDeck\PromptManager;

class OrderController extends Controller
{
    public function __construct(protected PromptManager $prompts) {}

    public function summarise(Request $request)
    {
        $prompt = $this->prompts->get('order-summary');
        // ...
    }
}
The PromptManager is registered as a singleton, so the same instance is reused throughout the request lifecycle.

Active version

Use the active method to explicitly load the active version:
$prompt = PromptDeck::active('order-summary');

$prompt->version(); // e.g. 3
This is equivalent to calling get() without a version number.

Specific version

Pass a version number as the second argument to get to load a specific version, regardless of which version is currently active:
$prompt = PromptDeck::get('order-summary', 1);

$prompt->version(); // 1
If the version does not exist, an InvalidVersionException is thrown.

Rendering roles

Once you have a PromptTemplate instance, you can render any role’s content with variable interpolation.

Dynamic role methods

The most expressive way to render a role is to call it as a method directly on the prompt instance. This uses PHP’s __call magic method and works for any role — not just system and user:
$prompt = PromptDeck::get('code-reviewer');

// Render the system role
$prompt->system(['tone' => 'professional']);

// Render the user role
$prompt->user(['input' => $code]);

// Render custom roles
$prompt->assistant(['context' => $history]);
$prompt->developer(['task' => 'review']);
$prompt->tool(['name' => 'search']);
When called without arguments, the content is returned with placeholders left intact:
$prompt->system(); // "You are a {{ $tone }} AI assistant..."

The role method

If the role name is dynamic or stored in a variable, use the explicit role method:
$roleName = 'assistant';

$content = $prompt->role($roleName, ['context' => $history]);
This is functionally identical to the magic method approach. If the role does not exist, an empty string is returned.

Raw content

To retrieve a role’s content without variable interpolation, use the raw method:
$template = $prompt->raw('system');
// "You are a {{ $tone }} AI assistant specialized in..."
This is useful when you need to inspect the template, store it, or perform your own interpolation logic.

Inspecting prompts

Available roles

The roles method returns an array of all role names defined in the prompt:
$prompt->roles();
// ['system', 'user', 'assistant']
Roles are discovered automatically from the files in the version directory. Any file matching the configured extension (e.g. .md) becomes a role — the filename (without extension) is the role name.

Checking for a role

Use the has method to check whether a specific role exists before attempting to render it:
if ($prompt->has('assistant')) {
    $content = $prompt->assistant(['context' => $history]);
}

Metadata

Each prompt version can carry metadata (stored in metadata.json at the version level). Access it via the metadata method:
$prompt->metadata();
// ['description' => 'Summarises customer orders', 'variables' => ['tone', 'input'], ...]
Metadata is an associative array. If no metadata.json exists in the version directory, an empty array is returned.

Name and version

$prompt->name();    // 'order-summary'
$prompt->version(); // 2

Building messages for AI APIs

All roles

The toMessages method builds a messages array compatible with OpenAI, Anthropic, and other chat-completion APIs. It renders every role with the given variables and returns them in definition order:
$messages = PromptDeck::get('chat-agent')->toMessages([
    'tone'  => 'helpful',
    'input' => $userMessage,
]);

// [
//     ['role' => 'system',    'content' => 'You are a helpful AI assistant...'],
//     ['role' => 'user',      'content' => 'Please help me with: ...'],
//     ['role' => 'assistant', 'content' => 'Based on the context...'],
// ]
This array can be passed directly to any AI API client:
// OpenAI
$response = OpenAI::chat()->create([
    'model'    => 'gpt-4o',
    'messages' => $messages,
]);

// Anthropic
$response = Anthropic::messages()->create([
    'model'    => 'claude-3-sonnet',
    'messages' => $messages,
]);

Filtering roles

Pass a second argument to limit which roles are included and in what order:
$messages = $prompt->toMessages(
    ['tone' => 'concise'],
    ['system', 'user']  // only include these two roles
);
Roles specified in the filter that don’t exist in the prompt are silently skipped.

Variable interpolation

Syntax

Prompt Deck supports two placeholder syntaxes within prompt files:
SyntaxExample
Spaced (recommended){{ $tone }}
Compact{{tone}}
Both are replaced when you render a role with variables:
// Template: "You are a {{ $tone }} AI assistant. Task: {{task}}"

$prompt->system(['tone' => 'friendly', 'task' => 'summarise']);
// "You are a friendly AI assistant. Task: summarise"
Use the spaced syntax ({{ $variable }}) for consistency with Laravel Blade conventions.

Supported value types

Values are cast to strings via PHP’s (string) cast, so you can pass any scalar or stringable value:
$prompt->system([
    'name'  => 'Alice',      // string
    'count' => 42,            // int → "42"
    'score' => 3.14,          // float → "3.14"
    'price' => '$100.00',     // string with special characters
]);

Missing variables

Placeholders that are not matched by the provided variables are left intact. This lets you render in stages or identify unfilled variables:
$prompt->system(['tone' => 'friendly']);
// "You are a friendly AI assistant. Your role is {{ $role }}."

Versioning

Directory structure

Prompts are versioned using directory-based versioning. Each version lives in its own sub-directory (v1/, v2/, etc.) inside the prompt folder:
resources/prompts/
└── order-summary/
    ├── v1/
    │   ├── system.md
    │   └── user.md
    ├── v2/
    │   ├── system.md
    │   ├── user.md
    │   └── assistant.md
    └── metadata.json
Each version directory can contain:
  • Any number of role files (e.g. system.md, user.md, assistant.md)
  • An optional metadata.json for version-level metadata

Listing versions

Retrieve all versions for a prompt programmatically:
$versions = PromptDeck::versions('order-summary');

// [
//     ['version' => 1, 'path' => '...', 'metadata' => [...]],
//     ['version' => 2, 'path' => '...', 'metadata' => [...]],
// ]
Versions are returned sorted in ascending order. Each entry includes the version number, the absolute path to the version directory, and any metadata from that version’s metadata.json. Or use the Artisan command:
php artisan prompt:list --all
See Artisan Commands — prompt:list for details.

Activating a version

Set a specific version as the active version:
PromptDeck::activate('order-summary', 2);
When database tracking is enabled, this updates the prompt_versions table — setting is_active = false on all versions of that prompt, then is_active = true on the specified version. When tracking is disabled, it writes the active_version key to the prompt’s root metadata.json file. Or use the Artisan command:
php artisan prompt:activate order-summary 2
See Artisan Commands — prompt:activate for details.

Version resolution order

When you call PromptDeck::get('name') without a version, the active version is resolved in this priority order:
  1. Database — If tracking is enabled, looks for a version marked is_active = true in the prompt_versions table for that prompt name.
  2. metadata.json — Reads the active_version key from the prompt’s root metadata.json file.
  3. Highest version — Falls back to the highest version number found on disk (e.g. if v1/ and v3/ exist, version 3 is used).
If no versions exist at all, an InvalidVersionException is thrown.

Caching

When caching is enabled, loaded prompts are stored in your configured cache store to avoid repeated filesystem reads:
// config/prompt-deck.php
'cache' => [
    'enabled' => env('PROMPTDECK_CACHE_ENABLED', true),
    'store'   => env('PROMPTDECK_CACHE_STORE', 'file'),
    'ttl'     => 3600, // seconds
    'prefix'  => 'prompt-deck:',
],
The cache key follows the pattern {prefix}{name}.v{version}. Prompts are cached on first load and served from cache on subsequent requests until the TTL expires. Caching is automatically disabled when APP_DEBUG=true to ensure file changes are picked up immediately during development. See Configuration — Cache for the full reference.

Execution tracking

When database tracking is enabled, you can log prompt executions for performance monitoring, A/B testing, and audit trails:
PromptDeck::track('order-summary', 2, [
    'input'    => ['message' => 'Summarise order #1234'],
    'output'   => 'Your order contains...',
    'tokens'   => 150,
    'latency'  => 234.5,
    'cost'     => 0.002,
    'model'    => 'gpt-4o',
    'provider' => 'openai',
    'feedback' => ['rating' => 5],
]);
All fields in the data array are optional. Records are inserted into the prompt_executions table. If tracking is disabled, the track method is a safe no-op. See Tracking & Performance for comprehensive documentation on the tracking system, database schema, and Eloquent models.
When using the Laravel AI SDK integration, the TrackPromptMiddleware handles execution tracking automatically — no manual track() calls needed.

Serialisation

The PromptTemplate class implements Laravel’s Arrayable contract. Call toArray() to get a serialisable representation:
$prompt->toArray();

// [
//     'name'     => 'order-summary',
//     'version'  => 2,
//     'roles'    => ['system' => '...', 'user' => '...'],
//     'metadata' => ['description' => '...'],
// ]
This is useful for caching, logging, debugging, or passing prompt data to queued jobs.