PHPackages                             ismailelbery/laravel-arabic-search - 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. [Database &amp; ORM](/categories/database)
4. /
5. ismailelbery/laravel-arabic-search

ActiveLibrary[Database &amp; ORM](/categories/database)

ismailelbery/laravel-arabic-search
==================================

Accurate Arabic text search for Laravel over plain MySQL / PostgreSQL / SQLite — no external search engine required.

v1.0.0(1mo ago)00MITPHPPHP ^8.1CI passing

Since Jul 3Pushed 1mo agoCompare

[ Source](https://github.com/IsmailElbery/laravel-arabic-search)[ Packagist](https://packagist.org/packages/ismailelbery/laravel-arabic-search)[ Docs](https://github.com/IsmailElbery/laravel-arabic-search)[ RSS](/packages/ismailelbery-laravel-arabic-search/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

Laravel Arabic Search
=====================

[](#laravel-arabic-search)

[![Latest Version on Packagist](https://camo.githubusercontent.com/b947c5f68c81a5baa2e10d84b614fae3728b5b594335aba37c725f2f0239e05b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f69736d61696c656c626572792f6c61726176656c2d6172616269632d7365617263682e737667)](https://packagist.org/packages/ismailelbery/laravel-arabic-search)[![Tests](https://camo.githubusercontent.com/b58240c4f639a66b1e0d5755e3df1f989706992c395dd20571e9f3245849752d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f49736d61696c456c626572792f6c61726176656c2d6172616269632d7365617263682f74657374732e796d6c3f6272616e63683d6d6173746572266c6162656c3d7465737473)](https://github.com/IsmailElbery/laravel-arabic-search/actions/workflows/tests.yml)[![Total Downloads](https://camo.githubusercontent.com/109686323a3b0b7c5d761cb196e765f67ab2ea11f47469fa7163bea7000e5c2c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f69736d61696c656c626572792f6c61726176656c2d6172616269632d7365617263682e737667)](https://packagist.org/packages/ismailelbery/laravel-arabic-search)[![PHP Version](https://camo.githubusercontent.com/7dec5a482c803d23b665a0791f120abed834740afe2d0c56cd2bcee59dcc83eb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f69736d61696c656c626572792f6c61726176656c2d6172616269632d7365617263682e737667)](https://packagist.org/packages/ismailelbery/laravel-arabic-search)[![License](https://camo.githubusercontent.com/2784a13ac7d44504b56b78b30ded6ce970cd49e7dfe2915559cff5e320b6fa86/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f49736d61696c456c626572792f6c61726176656c2d6172616269632d736561726368)](LICENSE)

**Arabic search that works on MySQL / PostgreSQL / SQLite — no Elasticsearch, no Meilisearch.**

Arabic text is written many ways for the same word: with or without diacritics, أ/إ/آ vs bare ا, ة vs ه, ى vs ي, and Persian/Urdu look-alike letters (ک, ی) that are *visually identical* to Arabic ones but sit at different Unicode codepoints. Naive `LIKE` misses all of these. This package normalizes both your stored text and the search term through the same pipeline, so `مكة` matches `مكه`, `مُحَمَّد` matches `محمد`, and `کتاب` (Persian keheh) matches `كتاب` (Arabic kaf).

You declare which columns are searchable **once** on the model; at query time you pass **only the search word**.

```
Article::arabicSearch('اسلام')->paginate(20);   // matches إسلام, الإسلام, ...
```

---

Installation
------------

[](#installation)

```
composer require ismailelbery/laravel-arabic-search
php artisan vendor:publish --tag=arabic-search-config
php artisan vendor:publish --tag=arabic-search-migrations
```

**Requirements:** PHP 8.1+, Laravel 10 / 11 / 12, and `ext-mbstring`. `ext-intl` is optional — if present it adds an NFKC pass that folds Arabic presentation forms from PDFs; a built-in map covers the common cases when it is absent.

---

Normalization rules
-------------------

[](#normalization-rules)

This table is the contract. Each rule is individually toggleable in `config/arabic-search.php`.

RuleTransformExampleDefault`strip_invisibles`remove zero-width &amp; bidi controls (U+200B–200F, U+061C, U+FEFF, …)`احمد‏` → `احمد`on`unicode_compatibility`NFKC / presentation forms &amp; ligatures`ﻻ` → `لا`on`strip_tashkeel`remove harakat &amp; Quranic marks (U+064B–065F, U+0670, U+06D6–06ED, …)`مُحَمَّد` → `محمد`on`strip_tatweel`remove kashida U+0640`محـــمد` → `محمد`on`normalize_alef`أ إ آ ٱ ٲ ٵ → ا`إسلام` → `اسلام`on`normalize_yeh`ى (maqsura), ی (Farsi), ے (Urdu) → ي`موسى`, `موسی` → `موسي`on`normalize_taa_marbuta`ة → ه`مكة` → `مكه`on`normalize_heh`ہ ۀ ە (Urdu/Persian) → ه`ہ` → `ه`on`normalize_kaf`ک ڪ (Persian/Urdu) → ك`کتاب` → `كتاب`on`normalize_waw`ؤ ۆ ۇ ۈ → و`مؤمن` → `مومن`on`normalize_hamza`ؤ → و, ئ → ي`قائم` → `قايم`on`strip_standalone_hamza`ء → (removed)`سماء` → `سما`**off**`normalize_dad_zah`ظ → ض (tolerant of a common misspelling)`ظل` → `ضل`**off**`normalize_digits`٠١٢٣ and ۰۱۲۳ (Persian) → 0123`٢٠٢٥` → `2025`on`lowercase_latin`lowercase mixed Latin`HeLLo` → `hello`on`collapse_whitespace`runs of whitespace → single, trim` محمد ` → `محمد`on**Design decision — recall over precision.** Normalization is intentionally lossy: `مكة` and `مكه` *will* match, by design. Precision is recovered by relevance ordering (exact &gt; prefix &gt; contains), not by being conservative here.

Two rules are **off by default because they are lossy across genuinely different words**, not just spelling variants of one letter — enable them only if you want that tolerance:

- `strip_standalone_hamza` — merges `سماء`/`سما`.
- `normalize_dad_zah` — folds ظ→ض, so `ظلّ` (shade) and `ضلّ` (to go astray) collide. Turn it on when your users frequently confuse the two letters. The toggle applies to **both** search paths (shadow column *and* `whereArabicVariants`) so they stay consistent.

Enable in `config/arabic-search.php`:

```
'rules' => [
    'normalize_dad_zah' => true,
],
```

Changing it changes the normalizer version — run `arabic-search:rebuild` afterwards for shadow-column tables.

Debug any term end-to-end:

```
php artisan arabic-search:inspect "مُحَمَّدٌ ٢٠٢٥"
```

---

Setup on a model
----------------

[](#setup-on-a-model)

1. Add the trait and list your searchable columns:

```
use IsmailElbery\ArabicSearch\Concerns\HasArabicSearch;

class Article extends Model
{
    use HasArabicSearch;

    protected array $arabicSearchable = ['title', 'body'];
}
```

2. Add the shadow columns. Edit the published migration (or write your own using the macro):

```
Schema::table('articles', function (Blueprint $table) {
    $table->arabicNormalized(['title', 'body']); // adds title_normalized, body_normalized
});
```

3. Backfill existing rows:

```
php artisan arabic-search:rebuild "App\Models\Article"
```

That's it. New/updated rows keep their shadow columns in sync automatically on save.

---

How it works
------------

[](#how-it-works)

You never search the original column. The package maintains a **normalized shadow column** next to it (`title` → `title_normalized`). On save, an observer normalizes the source into the shadow column; on search, the term is normalized with the **same pipeline** and matched against the shadow column. Because both sides run identical PHP normalization, they are guaranteed to agree — there is no SQL-vs-app drift.

```
articles
├── title              "مُحَمَّدٌ رسولُ الله"   ← original, shown to the user
└── title_normalized   "محمد رسول الله"         ← searched against

```

> ⚠️ **Bulk writes bypass model events.** `Model::query()->update()`, `insert()`, `upsert()` and raw SQL do **not** fire the observer, so the shadow columns go stale. Run `arabic-search:rebuild` afterwards.

---

Standalone normalizer (no model needed)
---------------------------------------

[](#standalone-normalizer-no-model-needed)

```
use IsmailElbery\ArabicSearch\Facades\ArabicSearch;

ArabicSearch::normalize('مُحَمَّدٌ');          // "محمد"
ArabicSearch::tokenize('بسم الله الرحمن');    // ["بسم","الله","الرحمن"]
```

---

Searching an existing table with no shadow column
-------------------------------------------------

[](#searching-an-existing-table-with-no-shadow-column)

Have a legacy `users` table you can't (or don't want to) alter? Use variant expansion — it matches every orthographic spelling directly against the **raw**column, no `_normalized` column and no `rebuild` needed:

```
User::whereArabicVariants('name', 'اسلام')->paginate();
DB::table('users')->whereArabicVariants('name', 'اسلام')->get();

// Two (or more) columns — OR-ed together:
User::whereArabicVariants(['first_name', 'last_name'], 'اسلام')->get();

// Composes with other conditions:
DB::table('docs')->where('pinned', true)
    ->orWhereArabicVariants('title', 'اسلام')->get();
```

Searching `اسلام` matches stored `اسلام`, `إسلام`, `أسلام`, `آسلام`, `الإسلام`, and diacritized/kashida spellings like `إِسْلَام` and `اســلام` — while correctly *not* matching a different word like `اسلم`. It works on MySQL, PostgreSQL and SQLite (a PCRE-backed `REGEXP` function is registered automatically for SQLite).

**When to use which:**

Shadow column (`HasArabicSearch`)Variant expansion (`whereArabicVariants`)Schema changeadds `_normalized` columnnoneBackfill`arabic-search:rebuild`noneMatching`LIKE` on the normalized columnregex on the raw columnUses an indexno in v1 (`LIKE` infix); fulltext plannedno (regex full-scan)Best fortables you ownlegacy/read-only tables, small–mediumConfiguration highlights
------------------------

[](#configuration-highlights)

KeyMeaning`term_logic``and` (all tokens must match, default) or `or` (any)`order_by_relevance`exact &gt; prefix &gt; contains ordering (default `true`)`min_token_length`tokens shorter than this are dropped (default `2`)`column_suffix`shadow-column suffix (default `_normalized`)`match_mode`reserved. v1 always uses `like`; index-backed `fulltext` is on the roadmap and not yet wiredChanging any rule changes the normalizer **version** (`ArabicSearch::version()`); rerun `arabic-search:rebuild` so stored data matches.

---

What this does NOT do (yet)
---------------------------

[](#what-this-does-not-do-yet)

Naming the limits earns more trust than hiding them:

- **No morphological / root analysis.** Searching `كتب` will not automatically find `مكتوب`/`كاتب`. (Root-based matching is a v2 maybe.)
- **No stemming** — light prefix/suffix stripping (ال، و، ب، ون، ين) is planned for v1.1, opt-in.
- **No synonym/fuzzy/Levenshtein** matching.
- **`LIKE` infix matches can't use an index** — great for small/medium tables. Index-backed `fulltext` matching is planned but not in v1; for very large datasets today, reach for a dedicated engine.

**Use this vs. Meilisearch/Typesense:** reach for this when you want correct Arabic matching on the database you already have, with zero extra infrastructure. Reach for a dedicated engine when you need typo-tolerance, faceting, or sub-10ms search over millions of rows.

---

Testing
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

The suite leads with an input → expected-output table (`NormalizationTest`) plus idempotency checks and an integration `SearchTest` against in-memory SQLite.

License
-------

[](#license)

MIT.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance90

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

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

Unknown

Total

1

Last Release

48d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/43496512?v=4)[IsmailElbery](/maintainers/IsmailElbery)[@IsmailElbery](https://github.com/IsmailElbery)

---

Top Contributors

[![IsmailElbery](https://avatars.githubusercontent.com/u/43496512?v=4)](https://github.com/IsmailElbery "IsmailElbery (8 commits)")

---

Tags

searchlaravelmysqlpostgresarabicnormalizationscout

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ismailelbery-laravel-arabic-search/health.svg)

```
[![Health](https://phpackages.com/badges/ismailelbery-laravel-arabic-search/health.svg)](https://phpackages.com/packages/ismailelbery-laravel-arabic-search)
```

###  Alternatives

[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k35.7M52](/packages/kirschbaum-development-eloquent-power-joins)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8723.3M27](/packages/yajra-laravel-oci8)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)

PHPackages © 2026

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