Filament plugin
Vibefilter Filtering beyond SQL
Filter any table by a plain-English statement.
Each row gets a calibrated probability.
composer require vibefilter/filament
MIT license · Bring your own API key (TypeSafe or OpenRouter)
Why
What SQL can't filter
A WHERE clause matches characters. Vibefilter asks a model whether the statement is true for each row, so it catches what a keyword never will.
Sarcasm
The customer is being sarcastic.
“Great job team, the desktop app just ate my unsaved filter set. Love losing twenty minutes of work :)”
LIKE '%great%' reads it as praise.
0.98 matchMeaning, not keywords
The customer is angry.
“I LOVE THIS LAMP!!! Colors are vibrant and it hasn't failed once.”
Caps and exclamation marks look angry to a rule.
0.02 no matchNo magic words
The customer blames the courier.
“Second time the parcel goes to the pickup point across town instead of my address. I'm done with this courier.”
You would need a keyword list for every way to say it.
0.95 matchHow it works
From a sentence to a filtered table
-
01
Add the filter
One line on any Filament table. Name the columns that hold the text.
VibeFilter::make() ->textColumns(['body']) -
02
Type a statement
Your users write what they are looking for, in plain English. "The customer is angry." "The ticket is about billing."
-
03
Rows get scored
Only the rows left by the other filters and the search go to TypeSafe Jev: 100 per request, 10 requests in parallel, under random tags, never with their IDs.
-
04
The table keeps what is true
Every row gets a calibrated probability. Rows at or above your threshold stay (0.8 by default). Scores are cached by content, so a statement you ran before is instant and free. Want to see each row's score? Add the optional score column:
VibeScoreColumn::make()
Live demo
Type a sentence. Watch the table listen.
The live demo has a table of 1,000 fictional product reviews. Pick an example, or type your own statement and watch a real model score the rows. Free, no sign-up.
- The customer is angry.
- The customer is being sarcastic.
- The customer blames the courier.
- The customer praises a support agent.
Install
Running in two minutes
PHP 8.2+, Laravel 12.36+ or 13, Filament 5.9+. No custom theme needed, light and dark mode both work.
1 · Install
composer require vibefilter/filament
2 · Create the score cache
php artisan vendor:publish --tag=vibefilter-migrations
php artisan migrate
3 · Add a key to .env
TYPESAFE_API_KEY=your-key
# or use Jev through OpenRouter
VIBEFILTER_DRIVER=openrouter
OPENROUTER_API_KEY=your-key
4 · Add the filter to a table
use Vibefilter\Filament\Tables\Filters\VibeFilter;
->filters([
VibeFilter::make()
->textColumns(['body']),
])
Thresholds, limits, drivers and testing with the fake driver: read the full README.
Built for real tables
Holds up when the API doesn't
- A cost guard
- Above a limit of unscored rows the filter asks first, with a Run anyway button. Nobody sends 50,000 rows by accident.
- Live progress and cost
- A progress bar streams the requests as they come back. With OpenRouter the summary shows exactly what the run cost.
- Private by design
- Only the columns you choose are sent, under random tags and in a shuffled order. Record IDs never leave your app.
- A cache by content
- Scores are stored by the text, the statement and the model. Equal texts are scored once, edited rows get a fresh score.
- Works with your other filters
- Only the rows left by the other filters and the search are scored, so a narrow table is fast and cheap.
- Survives a bad API day
- Rate limits and server errors are retried, a failing batch is split once, and the rows that came back still filter the table.
Results
Tested on 1,000 real-sounding reviews
Five language models (Claude, ChatGPT, Gemini, Grok and Mistral) wrote 1,000 reviews for a made-up shop and labelled their own mood and topic. We compared those labels with what Jev said.
- 861 / 1,000
- mood agreement at the 0.8 threshold
- 980 / 1,000
- topic agreement
- $0.003
- to score 1,000 short reviews
- ~1 s
- for 1,000 rows, 10 requests in parallel
Angry and satisfied were never swapped. Nearly every disagreement sits between neutral and mildly satisfied, where two people would argue too. The labels aren't ground truth, so these are agreement between two raters, not accuracy. Get the dataset →
Mentioned in