PHPackages                             pdro-lucas/filament-ai-writer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. pdro-lucas/filament-ai-writer

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

pdro-lucas/filament-ai-writer
=============================

AI Writer Action for Filament Forms — supports OpenAI, Claude and Gemini

v0.6.2(2mo ago)6509—10%MITPHPPHP ^8.1CI passing

Since May 10Pushed 2mo agoCompare

[ Source](https://github.com/pdro-lucas/filament-ai-writer)[ Packagist](https://packagist.org/packages/pdro-lucas/filament-ai-writer)[ RSS](/packages/pdro-lucas-filament-ai-writer/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (9)Dependencies (4)Versions (15)Used By (0)

filament-ai-writer
==================

[](#filament-ai-writer)

    untitled.mp4    A reusable Filament Action that adds AI-powered text generation to any form field. Supports Anthropic Claude, OpenAI, and Google Gemini out of the box.

> **Status: Beta** — This package is in active development. APIs may change and issues can occur.

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10, 11, or 12
- Filament 4.x

Installation
------------

[](#installation)

```
composer require pdro-lucas/filament-ai-writer
```

The service provider is auto-discovered by Laravel. No manual registration needed.

Configuration
-------------

[](#configuration)

Add your API key to `.env` depending on which provider you want to use:

```
# Anthropic (default)
ANTHROPIC_API_KEY=sk-ant-...

# OpenAI
OPENAI_API_KEY=sk-...

# Google Gemini
GEMINI_API_KEY=...
```

To switch providers or customize the model, add the following variables:

```
AI_WRITER_PROVIDER=anthropic   # anthropic | openai | gemini
AI_WRITER_MODEL=claude-sonnet-4-20250514
AI_WRITER_MAX_TOKENS=2048
AI_WRITER_CONNECT_TIMEOUT=10
AI_WRITER_TIMEOUT=60
AI_WRITER_RETRY_TIMES=2
AI_WRITER_RETRY_SLEEP_MS=500
```

If you need to customize the full configuration, publish the config file:

```
php artisan vendor:publish --tag=filament-ai-writer-config
```

This creates `config/filament-ai-writer.php` in your project where you can set per-provider models, API keys, and token limits. Publishing is optional — the package works with just the `.env` variables.

Usage
-----

[](#usage)

Import the action and attach it to any Filament form field using `hintAction` or `suffixAction`.

```
use PdroLucas\FilamentAiWriter\Actions\AiWriterAction;
```

The action works in two modes: **interactive** and **silent**. Interactive mode opens a modal where the user describes what they want. Silent mode reads other fields from the current form as context and generates immediately, with no modal.

### Interactive mode

[](#interactive-mode)

The user clicks the sparkles button, a modal opens with a free-text area, and the AI generates content based on the user input and the configured prompt. The result is injected into the target field.

```
use Filament\Forms\Components\MarkdownEditor;

MarkdownEditor::make('content')
    ->hintAction(
        AiWriterAction::make()
            ->targetField('content')
            ->prompt('
                You are a content writing expert.
                Write rich Markdown with headers, lists, and well-structured paragraphs.
                Return ONLY the Markdown content, no explanations.
            ')
    ),
```

```
use Filament\Forms\Components\Textarea;

Textarea::make('excerpt')
    ->hintAction(
        AiWriterAction::make('ai_excerpt')
            ->targetField('excerpt')
            ->prompt('
                Write a concise and engaging summary in 2-3 sentences.
                Tone: professional but accessible.
                Return ONLY the summary.
            ')
    ),
```

### Silent mode

[](#silent-mode)

The user clicks the button and the AI generates immediately, reading the specified context fields from the current form. No modal is shown. A success notification is displayed when generation completes.

If any of the context fields are empty when the button is clicked, the action shows a warning notification listing the missing fields and does not generate.

```
use Filament\Forms\Components\TextInput;

TextInput::make('slug')
    ->hintAction(
        AiWriterAction::make('ai_slug')
            ->targetField('slug')
            ->contextFields(['title'])
            ->prompt('
                Generate a URL-friendly slug based on the provided title.
                Return ONLY the slug, lowercase, hyphenated, no special characters.
            ')
            ->silent()
    ),
```

### Silent mode with a select field

[](#silent-mode-with-a-select-field)

Use `->valueMap()` when the target field is a select or radio. Pass the same `id => name` map used in `->options()`. The AI receives the available options as context and returns the key of the chosen option.

```
use Filament\Forms\Components\Select;

$categories = Category::all()->pluck('name', 'id');

Select::make('category_id')
    ->label('Category')
    ->options($categories)
    ->hintAction(
        AiWriterAction::make('ai_category')
            ->targetField('category_id')
            ->contextFields(['title', 'body'])
            ->valueMap($categories->toArray())
            ->prompt('
                Based on the title and body, choose the most appropriate category.
                Return ONLY the numeric ID of the chosen category, nothing else.
            ')
            ->silent()
    ),
```

### Silent mode with tags or multi-select

[](#silent-mode-with-tags-or-multi-select)

Use `->allowedValues()` when the target field is a tags input or multi-select. The AI receives the list of allowed values and must return a JSON array containing only values from that list. Values not present in the list are filtered out before injection.

```
use Filament\Forms\Components\TagsInput;

TagsInput::make('tags')
    ->hintAction(
        AiWriterAction::make('ai_tags')
            ->targetField('tags')
            ->contextFields(['title', 'body'])
            ->allowedValues(['laravel', 'filament', 'php', 'api', 'frontend', 'backend'])
            ->normalizeArrayCase()
            ->prompt('
                Suggest relevant tags based on the title and body.
                Return ONLY a raw JSON array of strings from the allowed list, no markdown, no explanation.
                Example: ["laravel","filament","php"]
            ')
            ->silent()
    ),
```

```
// Dynamically from the database
$tags = Tag::all()->pluck('name')->toArray();

TagsInput::make('tags')
    ->hintAction(
        AiWriterAction::make('ai_tags')
            ->targetField('tags')
            ->contextFields(['title', 'body'])
            ->allowedValues($tags)
            ->normalizeArrayCase()
            ->prompt('
                Suggest relevant tags based on the title and body.
                Return ONLY a raw JSON array of strings from the allowed list, no markdown, no explanation.
                Example: ["laravel","filament","php"]
            ')
            ->silent()
    ),
```

When no `->allowedValues()` is provided but the target field still expects an array, use `->expectArray()`:

```
TagsInput::make('tags')
    ->hintAction(
        AiWriterAction::make('ai_tags')
            ->targetField('tags')
            ->contextFields(['title', 'body'])
            ->expectArray()
            ->normalizeArrayCase()
            ->prompt('
                Suggest up to five relevant tags based on the title and body.
                Return ONLY a raw JSON array of strings, no markdown, no explanation.
                Example: ["laravel","filament","php"]
            ')
            ->silent()
    ),
```

API reference
-------------

[](#api-reference)

### `AiWriterAction::make(?string $name = null): static`

[](#aiwriteractionmakestring-name--null-static)

Creates a new action instance. Defaults to `'ai_writer'`. If you attach more than one action to the same form, give each a unique name.

### `->targetField(string $field): static`

[](#-targetfieldstring-field-static)

The name of the form field where the generated text will be injected.

### `->prompt(string $prompt): static`

[](#-promptstring-prompt-static)

The system prompt sent to the AI provider. Use this to define tone, format, length constraints, and domain-specific instructions. The more specific the prompt, the better the output.

### `->contextFields(array $fields): static`

[](#-contextfieldsarray-fields-static)

An array of field names to read from the current form and pass to the AI as context. Used in silent mode.

### `->silent(bool $condition = true): static`

[](#-silentbool-condition--true-static)

Enables silent mode. The action generates immediately on click with no modal, using `contextFields` as input.

### `->valueMap(array $map): static`

[](#-valuemaparray-map-static)

An `id => name` map for select or radio fields. The AI receives the options as context and returns a single key. Use this instead of `->allowedValues()` when the field expects a scalar value.

### `->allowedValues(array $values): static`

[](#-allowedvaluesarray-values-static)

A flat array of allowed values for tags inputs or multi-selects. The AI must return a JSON array containing only values from this list. Values not present in the list are filtered out before injection.

### `->expectArray(bool $condition = true): static`

[](#-expectarraybool-condition--true-static)

Tells the action to parse the AI response as an array even when no `->allowedValues()` is defined. Useful for free-form tags where any value is acceptable.

### `->normalizeArrayCase(bool $condition = true): static`

[](#-normalizearraycasebool-condition--true-static)

Converts all values in the generated array to lowercase before injection. Useful for tags to ensure consistent casing regardless of what the AI returns.

### `->beforeGenerate(callable $callback): static`

[](#-beforegeneratecallable-callback-static)

Register a callback that runs before AI generation on this action instance. Return `false` to cancel generation. Useful for credit checks, permissions, or rate limiting.

```
use Filament\Forms\Components\TextInput;

TextInput::make('meta_description')
    ->hintAction(
        AiWriterAction::make()
            ->targetField('meta_description')
            ->beforeGenerate(function (): bool {
                if (auth()->user()->credits prompt('Write a meta description.')
    ),
```

### `AiWriterAction::globalBeforeGenerate(callable $callback): void`

[](#aiwriteractionglobalbeforegeneratecallable-callback-void)

Register a global callback that runs before every AI generation across all action instances. Typically called in `AppServiceProvider::boot()`. The instance-level `->beforeGenerate()` runs after this global hook.

```
// AppServiceProvider
use PdroLucas\FilamentAiWriter\Actions\AiWriterAction;

public function boot(): void
{
    AiWriterAction::globalBeforeGenerate(function (): bool {
        if (auth()->user()->cannot('use-ai-writer')) {
            Notification::make()
                ->warning()
                ->title('AI writing is not available')
                ->send();
            return false;
        }
        return true;
    });
}
```

### `AiTextGenerated` event

[](#aitextgenerated-event)

Dispatched after every successful AI generation. The payload contains:

KeyDescription`user`The authenticated user (or null)`field`The target field name`provider`The AI provider name (e.g. `openai`)`model`The model used for generation`result`The raw text returned by the providerListen to this event to debit credits, log usage, or send notifications:

```
// app/Providers/EventServiceProvider.php
use PdroLucas\FilamentAiWriter\Events\AiTextGenerated;

protected $listen = [
    AiTextGenerated::class => [DebitUserCredits::class],
];
```

```
// app/Listeners/DebitUserCredits.php
class DebitUserCredits
{
    public function handle(AiTextGenerated $event): void
    {
        $user = $event->payload['user'];

        if ($user === null) {
            return;
        }

        $user->decrement('ai_credits');
    }
}
```

Supported providers
-------------------

[](#supported-providers)

ProviderDefault modelEnvironment variableAnthropic`claude-sonnet-4-20250514``ANTHROPIC_API_KEY`OpenAI`gpt-4o``OPENAI_API_KEY`Gemini`gemini-3.1-flash-lite``GEMINI_API_KEY`Package structure
-----------------

[](#package-structure)

```
src/
  Actions/
    AiWriterAction.php                  # The Filament Action
  Contracts/
    AiProvider.php                      # Interface for AI providers
  Events/
    AiTextGenerated.php                 # Event dispatched after generation
  Providers/
    AnthropicProvider.php
    OpenAiProvider.php
    GeminiProvider.php
  FilamentAiWriterServiceProvider.php
config/
  filament-ai-writer.php

```

Extending with a custom provider
--------------------------------

[](#extending-with-a-custom-provider)

Implement the `AiProvider` contract and rebind it in your `AppServiceProvider`:

```
use PdroLucas\FilamentAiWriter\Contracts\AiProvider;

public function register(): void
{
    $this->app->bind(AiProvider::class, fn () => new MyCustomProvider());
}
```

Local development
-----------------

[](#local-development)

To use this package in a local project, add a path repository to your project's `composer.json`:

```
{
    "repositories": [
        {
            "type": "path",
            "url": "../filament-ai-writer"
        }
    ],
    "require": {
        "pdro-lucas/filament-ai-writer": "@dev"
    }
}
```

Then run:

```
composer require pdro-lucas/filament-ai-writer @dev
```

Composer will symlink the local package directory, so any changes you make to the package are reflected immediately in the project.

Contributing
------------

[](#contributing)

Contributions are welcome! Feel free to open an issue or submit a pull request.

Before contributing, please ensure your changes follow the existing code style and include any necessary updates to documentation.

1. Fork the repository
2. Create a feature branch (`git checkout -b feat/my-feature`)
3. Commit your changes using [conventional commits](https://www.conventionalcommits.org/)
4. Push to your fork and open a pull request

License
-------

[](#license)

This project is open-sourced under the [MIT license](LICENSE.md).

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance86

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 75% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

9

Last Release

74d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8f1c03ddbad78c69b8532bbe5ed105c246b2efe11cbac62e408696d3168dab13?d=identicon)[Pedro Lucas](/maintainers/Pedro%20Lucas)

---

Top Contributors

[![pdro-lucas](https://avatars.githubusercontent.com/u/92560641?v=4)](https://github.com/pdro-lucas "pdro-lucas (39 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (9 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (2 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

aiclaudefilamentgeminiopenailaravelaiopenaifilamentGeminiclaude

### Embed Badge

![Health badge](/badges/pdro-lucas-filament-ai-writer/health.svg)

```
[![Health](https://phpackages.com/badges/pdro-lucas-filament-ai-writer/health.svg)](https://phpackages.com/packages/pdro-lucas-filament-ai-writer)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
