Library / SDK
teamtnt/laravel-scout-tntsearch-driver avatar
teamtnt/laravel-scout-tntsearch-driver

The TNTSearch driver: full-text search for Laravel Scout without a search server

Driver for Laravel Scout search package based on https://github.com/teamtnt/tntsearch

1,135 stars142 forksPHPMIT

At a glance

What is it?
teamtnt/laravel-scout-tntsearch-driver plugs the TNTSearch engine into Laravel Scout, so an application gets full-text search over its models without running Elasticsearch or Meilisearch. The configuration block is unusually well documented, and it is where you will spend your time deciding how forgiving the search should be.
Who is it for?
The TNTSearch driver fits a Laravel application that wants Scout's search interface over its own database, on a single server, with search good enough for users rather than a search product. It does not fit a multi-node deployment, a product whose users need a hosted search service's ranking and typo handling, or a query workload that the per-term document cap would truncate.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 82 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • teamtnt/laravel-scout-tntsearch-driver1,135 ★ · +1
Star history
Repository2026-10-222026-11-07
teamtnt/laravel-scout-tntsearch-driver1.1k1.1k

1 Stars in the last 16 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What the TNTSearch driver adds to a Laravel application

teamtnt/laravel-scout-tntsearch-driver is a driver for Laravel Scout, and Scout is the package that gives a Laravel model a uniform search interface regardless of which engine sits behind it. This driver points that interface at TNTSearch, a full-text search library written in PHP, and the README states that it makes it easy to add full text search support to your models for Laravel 5.3 through 13.0.

The problem it solves is a deployment one. Scout exists so an application can change search engines without rewriting its controllers, and its most common backing engines are separate services: Elasticsearch, Meilisearch, Algolia. Those bring their own hosting, their own JVM or binary, their own index lifecycle and their own operational questions. TNTSearch runs inside your PHP process, writes its index to files on disk, and needs nothing else running.

That is the right trade for a mid-sized application whose search needs are met by the data already in the database, and the wrong trade for one that needs ranking sophistication, typo tolerance at scale, faceting, or a search cluster.

The audience is Laravel developers who have already chosen Scout, or who want Scout's API without a separate service. The repository is small and conventional: src/ holds the driver, tests/ the test suite, composer.json and phpunit.xml the usual package files, with a .travis.yml from an earlier era of continuous integration. The project is published on Packagist, carries an MIT licence, and points to an Open Collective page for the wider TNTSearch project.

Installing the driver and wiring it into a Laravel application

The package installs through Composer:

bash
composer require teamtnt/laravel-scout-tntsearch-driver

Next the service providers. From Laravel 11 onwards they go in bootstrap/providers.php:

php
return [
    App\Providers\AppServiceProvider::class,
    // ...
    Laravel\Scout\ScoutServiceProvider::class,
    TeamTNT\Scout\TNTSearchScoutServiceProvider::class,
];

On earlier versions of Laravel the same two providers go into the providers array in config/app.php instead, and the README shows that form explicitly. The difference is the Laravel skeleton change rather than anything in the driver, but it is the step most likely to be missed when following a tutorial written for an older release.

The driver is then selected through the Scout environment variable, with SCOUT_DRIVER=tntsearch added to your .env file, and the Scout configuration published into your config directory:

bash
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

That publish step is what creates config/scout.php, and it is where the interesting part of this package lives. What you should see afterwards is a config/scout.php containing a tntsearch block alongside the engine-agnostic Scout settings.

The tntsearch configuration block, option by option

The README documents the tntsearch block in more detail than most drivers do, with a comment above every key explaining what it does and what it costs. The options that shape search behaviour are these:

php
'fuzziness' => env('TNTSEARCH_FUZZINESS', false),
'fuzzy' => [
    'prefix_length' => 2,
    'max_expansions' => 50,
    'distance' => 2,
    'no_limit' => true,
],
'asYouType' => false,
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
'maxDocs' => env('TNTSEARCH_MAX_DOCS', 500),
'stopwords' => [],

The storage key is separate, and it is the one with a hard constraint attached. The comment says the directory where the TNTSearch index files, the SQLite .index files, are written and read from, that it defaults to the app's storage path, and that it must be a local filesystem path. The comment also suggests pointing it elsewhere if the indexes should live on a separate local volume, for example a mounted disk, which is the difference between a local path and a shared network filesystem.

The fuzzy block is the tuning surface. prefix_length is the number of leading characters that must match exactly before fuzzy comparison starts, and the comment notes that higher values narrow the candidate word list and speed up fuzzy lookups. max_expansions caps how many candidate terms from the index wordlist, ordered by popularity, are pulled and compared by Levenshtein distance; lower is faster but may miss valid matches. distance is the maximum edit distance allowed between the search term and an indexed term, described in the comment as the number of single character insertions, deletions or substitutions. no_limit decides whether fuzzy matching runs even when the exact search already found results, in which case fuzzy matches are always added, or whether it only kicks in as a fallback.

maxDocs is the cap with the most surprising trade-off. It limits how many documents are fetched per matched keyword from the index, ordered by hit_count, before ranking and intersecting, and the comment is explicit that this is a per-term limit rather than a limit on final results. The consequence named there is that AND and OR boolean searches on very common words can miss legitimate matches, and that lower values improve performance on large indexes at the cost of search completeness.

stopwords excludes words from the index, with the example given as an array of articles and prepositions. The comment carries a warning that matters operationally: stopwords are applied at indexing time, so changing them requires reimporting your models.

Three search modes, and how they differ in behaviour

The driver supports three distinct ways of interpreting a query, and the config comments describe each precisely enough to choose between them.

The default is TNTSearch's normal free-text search. All words in the query are matched and the results are ranked by relevance, with no special meaning attached to or or to a leading minus sign. This is what most applications want by default.

searchBoolean switches to boolean parsing. Spaces between words mean AND, the word or surrounded by spaces means OR, a leading minus before a word means NOT, and parentheses group terms. Only documents matching the resulting expression are returned, with no relevance ranking. The README's examples make the difference concrete: with boolean parsing, "php -laravel" returns documents containing php but not laravel, and "cat or dog" returns documents containing either. With free-text search, the same input is treated as literal words and ranked by relevance, and the or and the minus sign carry no special weight.

asYouType is the third mode and the one built for instant search boxes. The last word of the query is treated as an incomplete word being typed, so instead of requiring an exact match it is matched as a prefix with a LIKE 'word%' pattern against the wordlist, picking the shortest and most popular matching term. All earlier words in the query still require exact matches. The comment notes this option can also be used on a per model basis, which is the behaviour to want when a search box on one model should behave differently from the rest.

The three modes compose with fuzziness, and that is where the results get interesting. With fuzziness on and no_limit true, a query gets both its exact matches and its Levenshtein-tolerant ones, which feels generous to a user and slow to a large index. With no_limit false, fuzziness only helps when the exact search found nothing, which is the conservative choice and the one most production applications end up with after looking at their logs.

Where the index lives, and how to keep it out of git

The index is a set of SQLite .index files, and the storage path defaults to the app's storage path. The configuration comment spells out the constraint that follows from the design: it must be a local filesystem path, and if the indexes should live on a separate local volume you point the setting at that mount.

That single requirement is the sharpest edge in the whole package, and it is worth understanding before you install rather than after. The index has to be somewhere the PHP process can read and write quickly, and it has to be a filesystem, not an object store and not a network share. A container with an ephemeral filesystem loses the index on every redeploy, and an application running on several nodes with a local index has a different index on each node, which is the kind of inconsistency that produces search results that depend on which server answered.

The README's advice on keeping the index out of version control is one line, added to .gitignore:

bash
/storage/*.index

Do that before the first import, because the index files are written as a side effect of indexing and the first commit after a fresh install is the one that catches them.

The model side is the other half. After installing Scout and this driver, you add the Searchable trait to the models you want to be searchable, and the README continues past that point. The release history shows this area of the driver evolving: v16.1.0 added stopwords support and is described as read-replica friendly, and v16.2.0 added whereIn and whereNotIn support, which is the kind of feature that matters when a Scout search is combined with a constrained query rather than used on its own.

Where this driver is the wrong tool

The first limit is horizontal scale. A local SQLite-backed index on a local filesystem is a single-node design. If your application runs on several servers behind a load balancer, the index has to live somewhere all of them can see and all of them can write, and the configuration explicitly says the path must be a local filesystem path. A shared network filesystem is tempting and wrong, since SQLite on NFS has locking behaviour nobody wants in production.

The second limit is ranking quality. The free-text mode ranks by relevance, and the fuzzy mode adds Levenshtein matches. That is a good search box for an internal tool or a catalogue of your own products. It is not the same as a dedicated search service, which brings its own ranking model, synonym handling, stemming, field weighting and typo dictionaries tuned for your corpus. A user who has used a good hosted search engine will notice the difference in ways the config cannot fully close.

The third limit is the per-term document cap, and it is the one most likely to bite without warning. maxDocs defaults to 500 documents per matched keyword, and the comment says that on very common words the top documents by popularity are the only ones considered, which can cause AND and OR boolean searches to miss legitimate matches. For a large index with a popular term, a lower cap is faster and less complete, and a higher cap is more complete and slower. There is no setting that removes the trade-off.

The fourth limit is the search surface. This driver is about matching documents to a query. It does not provide faceting, autocomplete endpoints, or query understanding beyond the three modes described above.

The alternative is the engine Scout was designed to abstract over. A hosted or self-hosted service such as Meilisearch or Elasticsearch sits behind the same Scout interface, so the difference in approach is where the index lives and who does the ranking work: the service keeps the index out of your filesystem, survives redeploys and node count changes, and does the heavy lifting for typo tolerance and relevance, at the price of another system to run and, for the hosted options, another bill.

Versioning, release practice and the licence

The release line moves with the Laravel and Scout versions it has to support, and the last three releases show exactly that. v16.1.0 on 2026-07-11 added stopwords support and is described as read-replica friendly, v16.1.1 on 2026-08-18 was a compatibility fix for Scout 11 where clauses, and v16.2.0 the same day added whereIn and whereNotIn support.

That pattern is the maintenance story for a Scout driver. The work is not feature work in the usual sense, it is keeping up with the framework, and the release titles are the changelog in miniature: a compatibility fix, a new filtering operator, an indexing option. The last push was on 2026-08-18, so the project is not dormant, but the release history is not continuous either, and the gaps between releases are the periods when a new Scout release might not be supported yet. Anyone running a bleeding-edge Laravel release should check that a driver release covering it exists before assuming compatibility, and the README's own headline range, Laravel 5.3 to 13.0, is the authority on which versions are claimed.

The licence is MIT, with LICENSE.txt in the repository root and a Packagist badge in the README. There is also a .travis.yml file, a leftover from the era when Travis was the default for open source PHP, and the current .github/ directory is where the project's automation now lives. The .github/ workflows are the CI that the badge points to, and the presence of both suggests the migration was done by adding rather than by removing.

On funding, the README points to an Open Collective page for the TNTSearch project and lists premium products from the same team. That is a reasonable signal about the project's sustainability, and it is also a reminder to look at who maintains it: this driver depends on TNTSearch itself, so a change in that engine's index format or configuration surface shows up here as a compatibility problem in a package whose version number tracks Laravel, not the search library.

Editorial conclusion

The TNTSearch driver fits a Laravel application that wants Scout's search interface over its own database, on a single server, with search good enough for users rather than a search product. It does not fit a multi-node deployment, a product whose users need a hosted search service's ranking and typo handling, or a query workload that the per-term document cap would truncate. Verify first that your index path is a local filesystem, add /storage/*.index to .gitignore before the first import, and decide the fuzzy.distance and searchBoolean settings on a copy of real query data, because the defaults are tuned for a forgiving consumer search and will return differently than a strict one.

Frequently asked questions

How do I install the TNTSearch driver for Laravel Scout?

Run composer require teamtnt/laravel-scout-tntsearch-driver, add the Scout and TNTSearch service providers to bootstrap/providers.php on Laravel 11 and newer or to config/app.php on earlier versions, set SCOUT_DRIVER=tntsearch in your .env file, and publish the Scout configuration with php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider".

Where does the TNTSearch driver store its search index?

In SQLite .index files written to the path configured by the storage key, which defaults to the app's storage path. The configuration comment states that it must be a local filesystem path and suggests pointing it at a separate local volume if the indexes should live elsewhere. The README also recommends adding /storage/*.index to .gitignore.

How do I enable typo-tolerant search with the TNTSearch driver?

Set the fuzziness option, which the configuration maps to the TNTSEARCH_FUZZINESS environment variable and which is false by default. Inside the fuzzy block, prefix_length sets how many leading characters must match exactly, max_expansions caps how many candidate terms are compared, distance sets the maximum Levenshtein distance, and no_limit decides whether fuzzy results are always added or only used as a fallback.

What licence is the TNTSearch Scout driver under, and what is the version history?

The package is licensed under MIT, with LICENSE.txt in the repository, and is published on Packagist as teamtnt/laravel-scout-tntsearch-driver. Recent releases are v16.1.0 on 2026-07-11 with stopwords support, v16.1.1 on 2026-08-18 for Scout 11 where compatibility, and v16.2.0 the same day with whereIn and whereNotIn support.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. teamtnt/laravel-scout-tntsearch-driver on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/teamtnt-laravel-scout-tntsearch-driver.svg)](https://hysenlabs.com/projects/teamtnt-laravel-scout-tntsearch-driver)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/teamtnt-laravel-scout-tntsearch-driver.svg)](https://hysenlabs.com/projects/teamtnt-laravel-scout-tntsearch-driver)

Was this useful?