naturalgroove/laravel-filament-image-generator-field
Laravel Filament Image Generator Field adds an AI-powered image generation field to Filament forms, letting users create images from text prompts directly in your admin panel. Includes configurable API integration and a simple, native field experience.
This is a Filament form field that lets an editor type a prompt, call an AI image API, and drop the result straight into a normal file-upload field. The whole workflow lives inside a Filament form, so start by getting it rendering on one resource before doing anything clever.
Install and publish config:
composer require naturalgroove/laravel-filament-image-generator-field
php artisan vendor:publish --tag="image-generator-field-config"
Add your provider credentials to .env (the package wraps the OpenAI image endpoint, so an OPENAI_API_KEY and billing on that account are required):
OPENAI_API_KEY=sk-...
Then add the field to any Filament form — a PostResource hero-image field is the fastest first use case:
use NaturalGroove\FilamentImageGeneratorField\ImageGeneratorField;
public static function form(Form $form): Form
{
return $form->schema([
TextInput::make('title')->required(),
ImageGeneratorField::make('image')
->label('Hero image')
->disk('public')
->directory('heroes'),
]);
}
Where to look first, in order:
config/image-generator-field.php — model, size, quality, disk, and any default prompt settings. This is the file you will actually touch.src/.../ImageGeneratorField.php) — confirms the exact fluent methods and the HTTP client/service it calls. The README shows the happy path; the class shows the real API surface.Note: verify exact class names and method names against the installed version. The package is small (16 stars, last release Dec 2024) and the public API has evolved; copy from the README of the tag you actually installed, not from blog posts.
Pair it with a real file field. The generator is most useful when it writes into standard Laravel storage that the rest of your app already understands. Either chain storage config directly onto the field, or pair it with FileUpload / Spatie Media Library so downstream code (image conversions, S3 sync, cleanup) keeps working unchanged.
ImageGeneratorField::make('image')
->disk('public') // must be a disk the browser can read
->directory('posts/heroes')
->visibility('public');
Build prompts from other form fields. The best day-to-day use is a prompt assembled from the record's own content, not free-typed each time. Use $get() with reactive fields so the prompt stays in sync:
TextInput::make('title')
->live(onBlur: true),
ImageGeneratorField::make('image')
->prompt(fn (Get $get) => "Editorial photo illustrating: {$get('title')}, no text, cinematic lighting"),
Keep the prompt builder pure and deterministic — it should not hit the DB during form hydration.
Generate into a draft, not production. Treat generation as an editorial step. Let the field write to a temp/draft disk or a pending directory, then promote on save. This makes "regenerate" cheap and keeps abandoned prompts from littering your media library.
Regeneration as a first-class action. Expose the field on edit forms and let editors overwrite. Store the prompt used (image_prompt column) alongside the image so you can show what produced it and re-run it later for a different size or style.
Gate it. Generation costs money per call. Wrap usage in authorization and make the field ->disabled(fn () => ! auth()->user()->can('generate-images')) or hide it entirely for non-editors. In multi-tenant apps, decide whether the API key is global or per-tenant before shipping.
Custom field subclass for house style. If every form needs the same model/size/quality defaults, subclass the field and set them once:
class BrandImageField extends ImageGeneratorField
{
protected function setUp(): void
{
parent::setUp();
$this->model('dall-e-3')->size('1024x1024');
}
}
Then use BrandImageField::make('image') everywhere and change defaults in one place.
Async where it matters. Generation is a slow network call (several seconds to tens of seconds). For an admin behind a spinner this is fine. For anything user-facing, move generation to a queued job and have the field display a "generating…" state — the field is a UI convenience, not a job runner.
max_execution_time, web server timeout, or Livewire request timeout is lower, the form will fail with an opaque 500 or a dropped connection. Raise timeouts on admin routes, or queue the work. This is the single most common "it doesn't work" report..env, run php artisan config:clear (and config:cache in production). A stale cached config is the usual cause of "invalid API key" on a server where the key is definitely set.1024x1024, 1024x1792, 1792x1024 for DALL-E 3). Passing an arbitrary size gives a provider-side 400 that surfaces as a vague validation error. Validate against the allowed list before calling.local disk "works" but the preview renders broken because the URL isn't publicly reachable. Use a public/S3 disk with public visibility, or generate a temporary URL for preview.setUp().php artisan filament:assets (and the package's own publish tag from the README). Panel registration via ->plugin(...) may or may not be needed depending on version — check the README for the installed tag.livewire-tmp and are pruned on a schedule. Don't depend on generated-but-unsaved images surviving; save the record to persist.Http::fake() on the provider endpoint (or Storage::fake() for the file side) so the suite is fast, deterministic, and free. Add one integration test behind an env flag for the real call.Http::onError. The field's notification often collapses the upstream error into a generic message; the raw response is what tells you it was a quota/429 versus a bad-key/401 versus a moderation/400.setUp() defaults, (b) the published config for model/size/disk, and (c) swapping the underlying service binding if the package uses the container. Don't monkey-patch vendor files — subclass instead, since the package updates without much ceremony and you want to absorb those.How can I help you explore Laravel packages today?