yousefaman / filament-smart-fill
Fill Filament forms from a PDF, an image or pasted text with AI, and review every value before it lands
Requires
- php: ^8.3
- filament/filament: ^4.15 || ^5.10
- laravel/ai: ^1.0
- spatie/laravel-package-tools: ^1.15
Requires (Dev)
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0 || ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Fill a Filament form from a PDF, an image or pasted text with AI, and review every value before it lands.
If this package saves you time, a star on GitHub helps other Filament developers find it.
Requirements
- PHP 8.3+
- Laravel 12.62+ or 13.15+ (the versions laravel/ai supports)
- Filament 4.15+ or 5.10+
- An AI provider configured in
config/ai.php
Tested combinations
CI runs on every push and pull request, and once a week to catch breakage from new Filament, Livewire or laravel/ai releases.
| PHP | Filament | Laravel (Testbench) |
|---|---|---|
| 8.3, 8.4 | 4.15+, 5.10+ | 12 (Testbench 10), 13 (Testbench 11) |
| 8.3 | 4.15 (lowest dependencies) | 12 (Testbench 10) |
The stable legs install the latest patch release of each version listed. The lowest leg installs the oldest versions the package allows.
Installation
composer require yousefaman/filament-smart-fill php artisan vendor:publish --tag=ai-config
Add the key of the provider you use to .env, for example:
OPENAI_API_KEY=your-key
Smart Fill uses the default provider from config/ai.php. To pick another one, see Usage.
Publish the package config (optional):
php artisan vendor:publish --tag="filament-smart-fill-config"
Publish the translations (optional):
php artisan vendor:publish --tag="filament-smart-fill-translations"
Usage
Add the action to the header of a Create or Edit page:
use Filament\Resources\Pages\CreateRecord; use YousefAman\FilamentSmartFill\Actions\SmartFillAction; class CreateInvoice extends CreateRecord { protected static string $resource = InvoiceResource::class; protected function getHeaderActions(): array { return [SmartFillAction::make()]; } }
The action opens a modal in two steps. In the first, the user uploads a document or pastes text. In the second, a review grid lists every field the model found, with the current value, the proposed value and a status. The user ticks the values to apply and clicks Apply. The values are written into the form; nothing is saved until the user clicks Save.
A field that already holds a different value shows as Changed and stays unticked, so the AI never overwrites by accident. An empty field shows as New and is ticked.
Action options
SmartFillAction::make() ->only(['number', 'customer_id', 'issued_on']) ->except(['internal_notes']) ->provider('openai', model: 'gpt-4o') ->instructions('Amounts are in US dollars. Dates in the document are day-first.') ->overwriteByDefault() ->acceptedFileTypes(['application/pdf', 'image/png']) ->maxFileSize(5120);
| Option | What it does |
|---|---|
only(array $statePaths) |
Fill only these fields. Paths are relative to the form, such as number or address.city. |
except(array $statePaths) |
Never fill these fields. |
provider($provider, model: null) |
The AI provider (a Laravel\Ai\Enums\Lab case or a name from config/ai.php) and, optionally, the model. Use this instead of model(), which Filament's Action already uses for the record's model class. |
instructions(string $text) |
Extra guidance added to the agent's instructions. |
overwriteByDefault() |
Tick Changed rows by default. |
acceptedFileTypes(array $mimeTypes) |
MIME types the upload accepts. Defaults to the config. |
maxFileSize(int $kilobytes) |
Largest accepted upload. Defaults to the config. |
When provider() is not called, the action uses SMART_FILL_PROVIDER and SMART_FILL_MODEL from the config, and falls back to the defaults in config/ai.php.
Field macros
use Filament\Forms\Components\TextInput; TextInput::make('reference') ->smartFillHint('The purchase order number, printed top right. Looks like PO-12345.'); TextInput::make('internal_code') ->smartFill(false);
smartFillHint()adds a hint for the model about where to find the value.smartFill(false)keeps a field out of Smart Fill. It accepts a closure.
Configuration
config/filament-smart-fill.php:
| Key | Default | Meaning |
|---|---|---|
provider |
env('SMART_FILL_PROVIDER') |
Provider name. null uses the default in config/ai.php. |
model |
env('SMART_FILL_MODEL') |
Model name. null uses the provider's default. |
timeout |
60 |
Seconds to wait for the provider. |
max_file_size |
10240 |
Upload limit in kilobytes. |
accepted_file_types |
PDF, PNG, JPEG, WebP, plain text | Accepted MIME types. |
max_text_length |
50000 |
Characters accepted in the paste-text field. |
review_ttl |
1800 |
Seconds a pending review is kept before it expires. |
Limits
The defaults above can be capped by settings outside the package:
- PHP upload limits. PHP's stock
upload_max_filesizeis2Mandpost_max_sizeis8M. Raise both abovemax_file_size(10 MB by default), or larger documents fail before Smart Fill sees them. - Livewire's temporary upload rule. Livewire validates every temporary upload with
max:12288(12 MB) unless you settemporary_file_upload.rulesinconfig/livewire.php. Amax_file_sizeabove 12 MB has no effect until you do. - Web server timeouts. Keep
timeout(60 seconds by default) below your web server and proxy timeouts (for nginx,fastcgi_read_timeoutorproxy_read_timeout, also 60 seconds by default). Otherwise a slow provider ends in a gateway timeout instead of Smart Fill's own message. - Rate limiting. Filament's
rateLimit()on the action only limits Apply. Reading the document happens on the wizard's Next, so it does not limit calls to your AI provider.
Supported fields
| Field | What the model returns | Notes |
|---|---|---|
TextInput (text, email, url, tel) |
text | The field's maxLength and input type are sent to the model. |
TextInput with numeric() or integer() |
number or integer | |
Textarea, MarkdownEditor |
text | |
DatePicker |
ISO 8601 date | Anything that is not ISO 8601 is dropped. Written in the picker's own format. |
DateTimePicker |
ISO 8601 date and time | As above. |
Toggle, Checkbox |
true or false | |
Select, Radio, ToggleButtons with options |
the key of one option | The model sees key = label pairs and can match either. |
Select with multiple(), CheckboxList |
a list of option keys | |
TagsInput |
a list of tags | |
Select with relationship() |
a name from the document | Resolved to a record, see below. |
Relationship selects
The model returns the name as it is written in the document. Smart Fill then looks it up through the select's own query, so modifyQueryUsing(), scopes and tenancy all apply, and the user is never offered a record they could not pick by hand.
- One exact match fills the field.
- Several matches show a Choose a match select in the review, with up to five candidates. The row stays unticked until the user picks one.
- No match lists the name under Not found. Nothing is created.
What is skipped
These are listed under Skipped in the review so the user knows they were not touched:
TimePickerRepeaterandBuilder, and everything inside themKeyValue,RichEditorandFileUpload- a
Selector option field with no options to choose from (for example, one fed only bygetSearchResultsUsing()without a relationship)
Hidden fields, disabled fields, read-only fields, password inputs, Hidden fields and fields marked smartFill(false) are never sent to the model and are not listed.
Providers
The document goes to the provider as an attachment. What the provider accepts depends on its gateway in laravel/ai, so check this table against the version you have installed (verified against laravel/ai 1.2.0).
| Provider | PDF documents | Images |
|---|---|---|
| OpenAI, Anthropic, Gemini, Mistral, xAI, OpenRouter | yes | yes |
Ollama, Groq, DeepSeek, openai-compatible |
no | yes |
A text file is sent the same way as a PDF, as a document, so it follows the PDF column.
When the provider cannot read the attachment, the user sees: "Your AI provider can't read this file type. Upload an image or paste the text instead." Pasted text works with every provider.
Whether a given model can read a document is up to the model, not the gateway. Pick a vision-capable model for images and scans.
Local servers (Ollama and openai-compatible) do not need an API key. For every other provider, a blank key shows "Smart fill isn't configured yet: set an AI provider in config/ai.php."
Testing in your app
Smart Fill uses a named agent, so laravel/ai's fakes work:
use YousefAman\FilamentSmartFill\Extraction\SmartFillAgent; SmartFillAgent::fake([['number' => 'INV-1']])->preventStrayPrompts();
The keys are the form-relative state paths of the fields, with each dot written as a double underscore (address.city becomes address__city). A test then calls the action as it would any Filament action, and no request leaves your machine.
Security and privacy
- The document goes to your AI provider. Smart Fill sends the uploaded file or pasted text, plus the labels and options of the fields it is filling, to the provider you configured. Do not use it with documents your provider is not allowed to see. Some providers store requests by default; for OpenAI, set
OPENAI_STORE=falseto turn that off. - Hidden, disabled, read-only and password fields are never filled. They are not sent to the model either, and every field is checked again right before its value is written.
- Nothing is saved without the user. Apply writes into the open form. The record changes only when the user clicks Save.
- Proposals stay on the server. The review is kept in your cache under a random token for
review_ttlseconds, and the token works only in the session that created it. The browser only holds the token, and an applied value must be one of the stored proposals (a relationship choice must be one of the offered candidates). - Uploads are checked and deleted. The upload accepts only the configured types, checked against the file's real MIME type, and the configured maximum size. The file is deleted right after extraction, also when extraction fails. Livewire keeps its own small metadata file for a temporary upload until its routine cleanup of temporary files runs.
- The document is treated as data. Pasted text is sent between
<document>tags, and the agent is told to treat the whole document as data and ignore instructions written inside it. This lowers the risk of prompt injection but cannot remove it, which is one more reason the user reviews every value.
Translations
Smart Fill ships in every language Filament ships (64 locales), so the action follows your panel's locale:
am ar az bg bn bs ca ckb cs da de el en es et eu fa fi fil fr he hi hr hu hy id it ja ka km ko ku lt lus lv mk mn ms my nb ne nl pl pt pt_BR ro ru sk sl sq sr_Cyrl sr_Latn sv sw tg th tr uk ur uz vi zh_CN zh_HK zh_TW
English and Arabic were written by hand. The other languages were machine-translated, reusing Filament's own wording for shared terms. If a string reads wrong in your language, a pull request to resources/lang/{locale}/smart-fill.php is very welcome.
Publish them to change any string:
php artisan vendor:publish --tag="filament-smart-fill-translations"
Changelog
Please see CHANGELOG for what has changed recently.
Contributing
Issues and pull requests are welcome. Run the tests with:
composer test
Security Vulnerabilities
Please do not report security vulnerabilities through public GitHub issues. Report them privately through GitHub's private vulnerability reporting instead. See SECURITY.md for details.
Credits
License
The MIT License (MIT). Please see License File for more information.
