PHPackages                             provydon/laravel-pgsearch - 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. [Search &amp; Filtering](/categories/search)
4. /
5. provydon/laravel-pgsearch

ActiveLibrary[Search &amp; Filtering](/categories/search)

provydon/laravel-pgsearch
=========================

PostgreSQL-friendly search for Eloquent (ILIKE + normalization; optional FTS/trigram later).

1.7.0(2mo ago)11.8k↓35.3%MITPHPPHP &gt;=8.1

Since Aug 8Pushed 2mo agoCompare

[ Source](https://github.com/provydon/laravel-pgsearch)[ Packagist](https://packagist.org/packages/provydon/laravel-pgsearch)[ RSS](/packages/provydon-laravel-pgsearch/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (8)Dependencies (14)Versions (12)Used By (0)

🔍 Laravel PostgreSQL Search
===========================

[](#-laravel-postgresql-search)

**Smart PostgreSQL search for Laravel with text normalization and relationship support.**

[![PHP Version](https://camo.githubusercontent.com/83dd395020c37276225039739320f6c8e7e99963ab21ee3d09282cb48dad2a60/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e312532422d626c7565)](https://php.net)[![Laravel Version](https://camo.githubusercontent.com/acd1a4186e10e5f9e8cc8bda4cd3331bbc23d551c7b972713953bca520d5fc0c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d3130253242253743313125324225374331322532422d726564)](https://laravel.com)[![PostgreSQL](https://camo.githubusercontent.com/ebe5c303bcb961016843157d402b743f8c2a5930e37efc8ab7be7fa0e2ae6380/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f506f737467726553514c2d436f6d70617469626c652d333336373931)](https://postgresql.org)[![License](https://camo.githubusercontent.com/5caa455d8debc46fb23abbadb45a733a937f3910a73fc875c2f7820468e1bb54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e)](LICENSE)[![Tests](https://camo.githubusercontent.com/214930dd574621ac62e96dbd4d3060592e9a26c296be4fb76e2fc63fe85546ef/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54657374732d50617373696e672d627269676874677265656e)](tests)

✨ Why This Package?
-------------------

[](#-why-this-package)

- 🎯 **Smart matching**: Find "Jane Doe" even when stored as "Jane-Doe"
- 📱 **Phone numbers**: Search "1234567890" matches "(123) 456-7890"
- 🔗 **Relationships**: Search across related models seamlessly
- ⚡ **PostgreSQL optimized**: Uses ILIKE and REGEXP\_REPLACE for performance
- 🛡️ **Safe fallback**: Works on non-PostgreSQL databases (no-op)

🚀 Quick Start
-------------

[](#-quick-start)

### Install

[](#install)

```
composer require provydon/laravel-pgsearch
```

### Use Immediately

[](#use-immediately)

```
// Search users
User::query()->pgSearch('john doe', ['name', 'email'])->get();

// Search with relationships
Post::query()->pgSearch('jane', ['title', 'user.name'])->get();

// Phone number search
User::query()->pgSearch('1234567890', ['phone'])->get();

// Or use the helper function
pg_search(User::query(), 'john doe', ['name', 'email'])->get();
```

That's it! No configuration needed.

💖 Support
---------

[](#-support)

If this package helped you, consider supporting its development:

[ ![Buy Me A Coffee](https://camo.githubusercontent.com/0cf29a542375e1a46e84d8bf5805a4e5c0a6ee98b6547ccdc0c55eed49d99c69/68747470733a2f2f63646e2e6275796d6561636f666665652e636f6d2f627574746f6e732f76322f64656661756c742d79656c6c6f772e706e67)](https://buymeacoffee.com/provydon)📖 Usage Examples
----------------

[](#-usage-examples)

### Basic Search

[](#basic-search)

```
// Single column
User::query()->pgSearch('john', ['name'])->get();

// Multiple columns
User::query()->pgSearch('example', ['name', 'email'])->get();
```

### Relationship Search

[](#relationship-search)

```
// Search posts by author name
Post::query()->pgSearch('jane doe', ['title', 'user.name'])->get();

// Search orders by customer info
Order::query()->pgSearch('smith', ['number', 'customer.name', 'customer.email'])->get();
```

### Advanced Options

[](#advanced-options)

```
// Disable text normalization
User::query()->pgSearch('exact-match', ['name'], ['normalize' => false])->get();

// Disable best-match ordering (default: true)
User::query()->pgSearch('office', ['name'], ['order_by_best_match' => false])->get();

// Chain with other query methods
User::query()
    ->where('active', true)
    ->pgSearch('john', ['name'])
    ->orderBy('created_at')
    ->paginate(15);
```

### Ordering and Custom Sort

[](#ordering-and-custom-sort)

When `order_by_best_match` is enabled (default), results are ranked by relevance: exact phrase match (100) &gt; normalized match (50) &gt; word token match (10). This prevents generic words (e.g. "office") from returning the wrong row when multiple rows match.

**Custom ordering**: Chain your `orderBy` **after** `pgSearch()` so relevance is primary and your column is the tiebreaker:

```
// ✓ Relevance first, then created_at
User::query()->pgSearch('john', ['name'])->orderBy('created_at', 'desc')->get();

// ✗ Your order wins; relevance only as tiebreaker
User::query()->orderBy('created_at')->pgSearch('john', ['name'])->get();
```

To disable best-match ordering entirely, pass `['order_by_best_match' => false]` or set it in config.

### Helper Function

[](#helper-function)

For convenience, you can also use the `pg_search()` helper function:

```
// Using the helper function
$users = pg_search(User::query(), 'john doe', ['name', 'email'])->get();

// With options
$users = pg_search(User::query(), 'john', ['name'], ['normalize' => false])->get();

// In controllers
public function search(Request $request)
{
    $query = User::query()->where('active', true);

    if ($request->has('search')) {
        $query = pg_search($query, $request->search, ['name', 'email']);
    }

    return $query->paginate(15);
}
```

🔧 Configuration (Optional)
--------------------------

[](#-configuration-optional)

Publish config to customize behavior:

```
php artisan vendor:publish --tag=pgsearch-config
```

```
// config/pgsearch.php
return [
    'normalize' => true, // Enable smart text matching (punctuation-stripped)

    'order_by_best_match' => true, // Order results by relevance (exact match > normalized > word matches)

    // Word-based matching (on normalized text)
    // When enabled, the search term is split into tokens and each
    // significant word is also searched individually. This lets
    // "Lagos State" match a record that only contains "Lagos", etc.
    'word_based_matching' => true,

    // NEW: Common suffixes ignored as standalone tokens when doing
    // word-based matching. Useful for geographic names:
    // "Lagos State" → token "lagos" (since "state" is ignored).
    'ignore_suffixes' => [
        'state',
        'province',
        'region',
        'territory',
        'city',
        'town',
        'municipality',
    ],
];
```

🧠 How It Works
--------------

[](#-how-it-works)

The package performs intelligent PostgreSQL searches:

Search TypeSQL ExampleMatches**Direct**`name ILIKE '%john doe%'`"John Doe", "JOHN DOE"**Normalized**`REGEXP_REPLACE(phone, '[^a-zA-Z0-9]', '', 'g') ILIKE '%1234567890%'`"(123) 456-7890", "123-456-7890"### Real-World Examples

[](#real-world-examples)

```
// These all find the same user:
User::query()->pgSearch('Jane Doe', ['name'])->get();      // Direct match
User::query()->pgSearch('jane doe', ['name'])->get();      // Case insensitive
User::query()->pgSearch('janedoe', ['name'])->get();       // Normalized match

// Phone number variations:
User::query()->pgSearch('1234567890', ['phone'])->get();   // Finds all these:
// "(123) 456-7890", "123-456-7890", "123.456.7890", "123 456 7890"
```

📋 Requirements
--------------

[](#-requirements)

- **Laravel**: 10.0+, 11.0+, or 12.0+
- **PHP**: 8.1+
- **Database**: PostgreSQL (graceful fallback for others)

⚡ Performance Tips
------------------

[](#-performance-tips)

For frequently searched columns, add expression indexes to speed up normalized searches:

```
-- For phone number searches
CREATE INDEX users_phone_normalized_idx
ON users (REGEXP_REPLACE(phone::text, '[^a-zA-Z0-9]', '', 'g'));

-- For name searches
CREATE INDEX users_name_normalized_idx
ON users (REGEXP_REPLACE(name::text, '[^a-zA-Z0-9]', '', 'g'));
```

**Important**: Use the exact same expression as in the search query for optimal performance.

🧪 Testing
---------

[](#-testing)

```
# Create test database
createdb pg-search

# Run tests
composer test
```

📝 License
---------

[](#-license)

MIT License - see [LICENSE](LICENSE) for details.

---

**Made with ❤️ for the Laravel community**

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance85

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~22 days

Recently: every ~55 days

Total

11

Last Release

60d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c596d5c1b7f297daa7dc1398beae75ea8f55ba06a89c2ef371cf3116df471673?d=identicon)[provydon](/maintainers/provydon)

---

Top Contributors

[![provydon](https://avatars.githubusercontent.com/u/31216637?v=4)](https://github.com/provydon "provydon (15 commits)")

---

Tags

laravelpostgressearch

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/provydon-laravel-pgsearch/health.svg)

```
[![Health](https://phpackages.com/badges/provydon-laravel-pgsearch/health.svg)](https://phpackages.com/packages/provydon-laravel-pgsearch)
```

###  Alternatives

[swisnl/laravel-fulltext

Fulltext indexing and searching for Laravel

184104.5k6](/packages/swisnl-laravel-fulltext)[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)[baijunyao/laravel-scout-elasticsearch

Elasticsearch Driver for Laravel Scout

8023.7k1](/packages/baijunyao-laravel-scout-elasticsearch)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

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