PostgreSQL JSONB in Laravel: Index, Query &amp; Cast | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Bloat        On this page       1. [  Why JSONB Over JSON (and Over EAV) ](#why-jsonb-over-json-and-over-eav)
2. [  GIN Indexes: The Key to Fast JSONB Queries ](#gin-indexes-the-key-to-fast-jsonb-queries)
3. [  Querying JSONB with Eloquent ](#querying-jsonb-with-eloquent)
4. [  Custom Eloquent Casts for Typed JSONB ](#custom-eloquent-casts-for-typed-jsonb)
5. [  Partial GIN Indexes for High-Cardinality Tables ](#partial-gin-indexes-for-high-cardinality-tables)
6. [  Avoiding Common Pitfalls ](#avoiding-common-pitfalls)
7. [  Takeaways ](#takeaways)

  ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Bloat](https://cdn.msaied.com/528/3c1e1dba1e2f4ca6b8686cf670dcd3b8.png)

  #laravel   #postgresql   #jsonb   #eloquent   #performance  

 PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Bloat 
================================================================================

     9 Aug 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why JSONB Over JSON (and Over EAV)  ](#why-jsonb-over-json-and-over-eav)
2. [  02   GIN Indexes: The Key to Fast JSONB Queries  ](#gin-indexes-the-key-to-fast-jsonb-queries)
3. [  03   Querying JSONB with Eloquent  ](#querying-jsonb-with-eloquent)
4. [  04   Custom Eloquent Casts for Typed JSONB  ](#custom-eloquent-casts-for-typed-jsonb)
5. [  05   Partial GIN Indexes for High-Cardinality Tables  ](#partial-gin-indexes-for-high-cardinality-tables)
6. [  06   Avoiding Common Pitfalls  ](#avoiding-common-pitfalls)
7. [  07   Takeaways  ](#takeaways)

 Why JSONB Over JSON (and Over EAV)
----------------------------------

PostgreSQL's `jsonb` type stores JSON in a decomposed binary format. Reads are faster than `json`, and — critically — you can index it. For Laravel applications that need flexible per-tenant settings, feature flags, or dynamic product attributes, `jsonb` is almost always the right call over an EAV table or a plain `text` column.

```sql
-- Migration
Schema::table('products', function (Blueprint $table) {
    $table->jsonb('attributes')->nullable();
});

```

---

GIN Indexes: The Key to Fast JSONB Queries
------------------------------------------

Without an index, every JSONB query is a full table scan. A GIN (Generalized Inverted Index) index covers containment and existence operators.

```sql
-- Raw migration statement
DB::statement('CREATE INDEX products_attributes_gin ON products USING GIN (attributes)');

```

For queries that target a single known key path, a functional B-tree index is cheaper:

```sql
DB::statement(
    "CREATE INDEX products_attributes_color ON products ((attributes->>'color'))"
);

```

Use `EXPLAIN (ANALYZE, BUFFERS)` to confirm the planner picks your index.

---

Querying JSONB with Eloquent
----------------------------

Laravel ships with first-class JSONB helpers that map to PostgreSQL operators.

```php
// Containment: attributes @> '{"color": "red"}'
Product::whereJsonContains('attributes->color', 'red')->get();

// Key existence: attributes ? 'warranty'
Product::whereJsonContainsKey('attributes->warranty')->get();

// Numeric comparison via path extraction
Product::whereRaw("(attributes->>'weight')::numeric > ?", [5.0])->get();

// Ordering by a JSONB path
Product::orderByRaw("attributes->>'sort_order' ASC NULLS LAST")->get();

```

`whereJsonContains` generates the `@>` containment operator, which the GIN index can satisfy. The `->>'key'` extraction casts to text; add `::numeric` or `::int` for numeric comparisons.

---

Custom Eloquent Casts for Typed JSONB
-------------------------------------

Raw arrays are fine for prototyping, but a typed cast keeps your domain clean.

```php
// app/Casts/ProductAttributes.php
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class ProductAttributes implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): \App\Data\ProductAttributesData
    {
        return \App\Data\ProductAttributesData::fromArray(
            json_decode($value ?? '{}', true)
        );
    }

    public function set($model, string $key, $value, array $attributes): string
    {
        if ($value instanceof \App\Data\ProductAttributesData) {
            return json_encode($value->toArray());
        }
        return json_encode($value);
    }
}

```

```php
// app/Models/Product.php
protected $casts = [
    'attributes' => ProductAttributes::class,
];

```

Now `$product->attributes` returns a strongly typed value object, not a plain array. IDE autocompletion works, and you can add validation logic inside `ProductAttributesData`.

---

Partial GIN Indexes for High-Cardinality Tables
-----------------------------------------------

If only a subset of rows have meaningful JSONB data, a partial index reduces index size and write overhead:

```sql
DB::statement(
    "CREATE INDEX products_attributes_active_gin
     ON products USING GIN (attributes)
     WHERE attributes IS NOT NULL AND status = 'active'"
);

```

The planner will use this index only when the `WHERE` clause matches, keeping it lean.

---

Avoiding Common Pitfalls
------------------------

- **Type coercion**: JSONB stores numbers as numeric, but `->>'key'` always returns text. Cast explicitly in SQL.
- **Deep nesting**: Deeply nested paths (`attributes->'specs'->'dimensions'->>'width'`) are harder to index. Flatten where possible.
- **Migrations on large tables**: Adding a GIN index locks the table. Use `CREATE INDEX CONCURRENTLY` via `DB::statement` in a separate migration.
- **Eloquent `update` with JSONB**: `$model->update(['attributes->color' => 'blue'])` uses PostgreSQL's `jsonb_set` under the hood in Laravel 10+. Verify with query logging.

---

Takeaways
---------

- Use `jsonb`, not `json`; the binary format enables indexing.
- Add a GIN index for containment queries; use functional B-tree indexes for single-key lookups.
- `whereJsonContains` maps to `@>` and is index-aware.
- Wrap JSONB columns in a custom `CastsAttributes` implementation for type safety.
- Use `CREATE INDEX CONCURRENTLY` on production tables to avoid locks.
- Profile every JSONB query with `EXPLAIN (ANALYZE, BUFFERS)` before shipping.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fpostgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-bloat-1&text=PostgreSQL+JSONB+in+Laravel%3A+Indexing%2C+Querying%2C+and+Casting+Without+the+Bloat) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fpostgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-bloat-1) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does Laravel's whereJsonContains use a GIN index automatically?        Yes, when you have a GIN index on the JSONB column, PostgreSQL's query planner will use it for containment queries generated by whereJsonContains. Always verify with EXPLAIN ANALYZE, as the planner may still choose a sequential scan on small tables. 

      Q02  Should I use jsonb or a separate relational table for dynamic attributes?        Use jsonb when the attribute schema varies per row and you rarely need to join or aggregate on individual attribute keys. Use a relational table when you need foreign keys, strong typing, or frequent cross-row aggregations on specific attributes. 

      Q03  How do I update a single JSONB key without overwriting the whole column in Laravel?        In Laravel 10+, you can use dot-notation: $model-&gt;update(['attributes-&gt;color' =&gt; 'blue']). Laravel compiles this to a jsonb_set call, so only the targeted key is modified. Check your query log to confirm the generated SQL. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Bloat](https://cdn.msaied.com/527/ccaba2b97bd9c80bd87c9e4886481cb6.png) laravel postgresql eloquent 

### PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Bloat

JSONB columns unlock flexible schemas without sacrificing query performance. Learn how to index, query, and ca...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 9 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-bloat) [ ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Chaos](https://cdn.msaied.com/526/bc43aae3afe723f9a29f47820735edf5.png) laravel postgresql jsonb 

### PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Chaos

JSONB columns unlock flexible schemas, but without the right indexes and Eloquent integration they become a pe...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 9 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-chaos-2) [ ![Filament v4 Schema-Based Forms: Practical Patterns for the Unified Schema API](https://cdn.msaied.com/525/44fb6fe80b4b2439c1b1d9124976c67d.png) filament laravel filament-v4 

### Filament v4 Schema-Based Forms: Practical Patterns for the Unified Schema API

Filament v4 replaces scattered form/infolist definitions with a single Schema API. This post walks through rea...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 8 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/filament-v4-schema-based-forms-practical-patterns-for-the-unified-schema-api) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
