PHPackages                             micka-17/typesense-bundle - 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. [API Development](/categories/api)
4. /
5. micka-17/typesense-bundle

ActiveSymfony-bundle[API Development](/categories/api)

micka-17/typesense-bundle
=========================

Un bundle Symfony pour intégrer Typesense.

v2.0.5(1mo ago)050MITPHPPHP &gt;=8.5CI passing

Since Jul 3Pushed 1mo agoCompare

[ Source](https://github.com/Micka-17/typesense-bundle)[ Packagist](https://packagist.org/packages/micka-17/typesense-bundle)[ RSS](/packages/micka-17-typesense-bundle/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (26)Versions (9)Used By (0)

micka-17/typesense-bundle
=========================

[](#micka-17typesense-bundle)

Symfony bundle for [Typesense](https://typesense.org) — full-text search, vector search, and AI-powered search for your Doctrine entities.

[![PHP](https://camo.githubusercontent.com/c0cd2fac930abe3f83e13dff488a34edef374651e38cf83eb06f6b43d1f6693e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e352532422d626c7565)](https://php.net)[![Symfony](https://camo.githubusercontent.com/9f13c2e458d47ef1be0f1fcba09213fb268752c9a0bcf9a3baba36ccd3730d3e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53796d666f6e792d372e34253230253743253230382e302d626c61636b)](https://symfony.com)[![Typesense](https://camo.githubusercontent.com/57fb5fdf009c56e261b5dbbc93968f5315ad4260110c006bf26fa30fafd481c4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5479706573656e73652d33302532422d6f72616e6765)](https://typesense.org)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

Compatibility
-------------

[](#compatibility)

BundlePHPSymfonyTypesense Servertypesense-php2.x≥8.5^7.4 | ^8.0≥30^6.01.x≥8.1^6.4 | ^7.026–29^5.0---

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

[](#installation)

```
composer require micka-17/typesense-bundle
```

Register the bundle (if not using Symfony Flex):

```
// config/bundles.php
return [
    Micka17\TypesenseBundle\TypesenseBundle::class => ['all' => true],
];
```

---

Quick Start
-----------

[](#quick-start)

### 1. Configure the bundle

[](#1-configure-the-bundle)

```
# config/packages/typesense.yaml
typesense:
    api_key: '%env(TYPESENSE_API_KEY)%'
    cluster:
        nodes:
            - { host: '%env(TYPESENSE_HOST)%', port: 8108, protocol: http }

    indexable_entities:
        - App\Entity\Product
        - App\Entity\Category

    auto_update: true   # or: {enabled: true, mode: sync|async}
```

```
# .env
TYPESENSE_API_KEY=xyz
TYPESENSE_HOST=localhost
```

### 2. Annotate your entities

[](#2-annotate-your-entities)

```
use Micka17\TypesenseBundle\Attribute\TypesenseIndexable;
use Micka17\TypesenseBundle\Attribute\TypesenseField;

#[TypesenseIndexable(collection: 'products')]
class Product
{
    #[TypesenseField(type: 'string', sort: true)]
    private string $name;

    #[TypesenseField(type: 'float', facet: true)]
    private float $price;

    #[TypesenseField(type: 'string[]', facet: true)]
    private array $tags = [];

    #[TypesenseField(type: 'int32')]
    private int $stock;
}
```

### 3. Create collections and index data

[](#3-create-collections-and-index-data)

```
php bin/console micka17:typesense:sync
```

### 4. Search

[](#4-search)

```
use Micka17\TypesenseBundle\Service\FinderService;

class ProductController
{
    public function __construct(private readonly FinderService $finder) {}

    public function search(string $q): array
    {
        $result = $this->finder->search('products', [
            'q'        => $q,
            'query_by' => 'name',
            'filter_by' => 'stock:>0',
        ]);

        return $result->hits; // array of documents
    }
}
```

---

Configuration Reference
-----------------------

[](#configuration-reference)

```
typesense:
    api_key: '%env(TYPESENSE_API_KEY)%'

    # --- Cluster ---
    cluster:
        enabled: false                  # true for multi-node HA setup
        nodes:
            - { host: localhost, port: 8108, protocol: http }
        read_preference: nearest        # nearest | leader | follower
        consistency_level: eventual     # eventual | strong

    # --- Entities to index ---
    indexable_entities:
        - App\Entity\Product
        - App\Entity\Category

    # --- Auto-index on Doctrine events ---
    auto_update: true

    # --- Error tracking ---
    error_tracking:
        enabled: true
        log_level: error
        track_node_errors: false
        node_error_fields: [host, port, error_message]

    # ── V2 Resources (Typesense 30+) ──────────────────────────────────────

    synonym_sets:
        main:
            items:
                electronics:
                    synonyms: [phone, mobile, smartphone]
                size:
                    root: large
                    synonyms: [big, huge, xl, xxl]

    curation_sets:
        featured:
            items:
                promote-iphone:
                    rule: { query: iphone }
                    includes:
                        - { id: 'product-42', position: 1 }

    presets:
        product_default:
            value:
                query_by: name,description
                per_page: 20
                sort_by: _text_match:desc

    stemming_dictionaries:
        french:
            words:
                - { word: chaussures, root: chaussure }
                - { word: couraient, root: courir }

    analytics_rules:
        popular_products:
            type: popular_queries
            params:
                source:
                    collections: [products]
                destination:
                    collection: popular_queries

    nl_search_models:
        products-nl:
            model_name: openai/gpt-4o-mini
            api_key: '%env(OPENAI_API_KEY)%'
            system_prompt: 'You translate natural language into Typesense search parameters.'

    conversation_models:
        support-bot:
            model_name: openai/gpt-4o-mini
            api_key: '%env(OPENAI_API_KEY)%'
            system_prompt: 'You are a helpful product assistant.'

    # --- Legacy (V1 only, migrate to synonym_sets) ---
    # synonyms: []
```

---

Attributes
----------

[](#attributes)

### `#[TypesenseIndexable]`

[](#typesenseindexable)

Applied on the entity class. Defines the Typesense collection.

ParameterTypeDefaultDescription`collection`string—Collection name in Typesense`defaultSortingField`string—Field used for default sort (must be numeric)`normalizerMethod`string—Custom method on the entity to build the document`metadata`array\[\]Arbitrary metadata attached to the collection`options`array\[\]Extra collection-level options (voice\_query\_model…)### `#[TypesenseField]`

[](#typesensefield)

Applied on entity properties. Maps a property to a Typesense field.

ParameterTypeDefaultDescription`type`stringautoTypesense field type (`string`, `int32`, `float`, `bool`, `string[]`, `float[]`, `auto`, …)`name`string—Override the field name in the index`facet`boolfalseEnable faceting`sort`boolfalseEnable sorting`optional`boolfalseAllow null values`index`booltrueInclude field in the index`getter`string—Method name to call instead of reading the property`reference`string—JOIN reference, format `collection.field``asyncReference`boolfalseAsync JOIN reference`cascadeDelete`boolfalseDelete related document on parent delete`embed`array—Auto-embedding config (requires `type: auto`)`numDim`int—Vector dimension (required for `float[]` fields)`vecDist`string—Vector distance metric: `cosine`, `ip`, `l2sq``hnswParams`array—HNSW index parameters`truncate`int—Max characters (string fields only)`tokenSeparators`string\[\]\[\]Custom token separators`symbolsToIndex`string\[\]\[\]Symbols to include in the index**Examples:**

```
// Vector field with auto-embedding
#[TypesenseField(
    type: 'float[]',
    embed: ['from' => ['name', 'description'], 'model_config' => ['model_name' => 'ts/e5-small']],
    numDim: 384,
    vecDist: 'cosine',
)]
private array $embedding;

// JOIN reference
#[TypesenseField(type: 'string', reference: 'brands.id')]
private string $brandId;

// Custom getter
#[TypesenseField(type: 'string[]', getter: 'getTagNames')]
private Collection $tags;
```

---

FinderService
-------------

[](#finderservice)

Inject `Micka17\TypesenseBundle\Service\FinderService` and use these methods:

### `search(string $collection, array $params): Result`

[](#searchstring-collection-array-params-result)

```
$result = $finder->search('products', ['q' => 'laptop', 'query_by' => 'name']);
$result->found      // int: total matches
$result->hits       // array of documents
$result->tookMs     // int: query time
$result->facetCounts // array of facet buckets
```

### `searchAndPaginate(string $collection, array $params, int $page, int $perPage): Paginator`

[](#searchandpaginatestring-collection-array-params-int-page-int-perpage-paginator)

```
$paginator = $finder->searchAndPaginate('products', ['q' => 'laptop'], page: 2, perPage: 15);
$paginator->items        // array of documents
$paginator->total        // int
$paginator->currentPage  // int
$paginator->lastPage     // int (virtual, via property hook)
$paginator->hasNextPage  // bool
$paginator->nextPage     // ?int
```

### `multiSearch(array $searchRequests): Result[]`

[](#multisearcharray-searchrequests-result)

```
$results = $finder->multiSearch([
    'searches' => [
        ['collection' => 'products', 'q' => 'laptop', 'query_by' => 'name'],
        ['collection' => 'categories', 'q' => 'laptop', 'query_by' => 'name'],
    ],
]);
```

### `searchWithPreset(string $presetName, array $extra = []): Result`

[](#searchwithpresetstring-presetname-array-extra---result)

```
$result = $finder->searchWithPreset('product_default');
```

### `unionSearch(array $searches, array $commonParams = []): Result` *(v30+)*

[](#unionsearcharray-searches-array-commonparams---result-v30)

Searches multiple collections and merges results into a single ranked list. Each hit includes `_collection`.

```
$result = $finder->unionSearch([
    ['collection' => 'books',   'q' => 'harry potter', 'query_by' => 'title'],
    ['collection' => 'movies',  'q' => 'harry potter', 'query_by' => 'title'],
], ['per_page' => 10]);
```

### `searchWithDiversification(string $collection, array $params, float $mmrLambda = 0.5, ?string $mmrEmbeddingField = null): Result` *(v30+)*

[](#searchwithdiversificationstring-collection-array-params-float-mmrlambda--05-string-mmrembeddingfield--null-result-v30)

MMR (Maximal Marginal Relevance) diversification — reduces duplicate results. `mmrLambda = 1.0` → max relevance, `0.0` → max diversity.

```
$result = $finder->searchWithDiversification(
    'products',
    ['q' => 'laptop', 'vector_query' => 'embedding:([0.1, ...])'],
    mmrLambda: 0.7,
    mmrEmbeddingField: 'embedding',
);
```

### `conversationalSearch(string $collection, array $params, string $modelId, ?string $conversationId = null): Result` *(v30+)*

[](#conversationalsearchstring-collection-array-params-string-modelid-string-conversationid--null-result-v30)

RAG-style search. The model answers the query and the answer is in `$result->conversationAnswer`.

```
$result = $finder->conversationalSearch('products', ['q' => 'best laptop for gaming'], 'support-bot');
echo $result->conversationAnswer;   // "Based on our catalog, the ASUS ROG..."
echo $result->conversationId;       // "conv-abc123" — pass on follow-ups

// Follow-up:
$result2 = $finder->conversationalSearch('products', ['q' => 'what about battery life?'], 'support-bot', $result->conversationId);
```

### `naturalLanguageSearch(string $collection, string $query, string $modelId, array $extra = []): Result` *(v30+)*

[](#naturallanguagesearchstring-collection-string-query-string-modelid-array-extra---result-v30)

Translates a free-text question into structured search parameters server-side.

```
$result = $finder->naturalLanguageSearch(
    'products',
    'laptops under 1000 euros with good battery',
    'products-nl',
    ['per_page' => 5],
);
```

---

Commands
--------

[](#commands)

### Sync &amp; maintenance

[](#sync--maintenance)

```
# Create/update collections + apply all V2 resources in one shot
php bin/console micka17:typesense:sync

# Diagnose configuration vs Typesense state
php bin/console micka17:typesense:doctor

# Re-index a specific entity (paginated batches, OOM-safe)
php bin/console typesense:reindex "App\Entity\Product"
php bin/console typesense:reindex "App\Entity\Product" --batch-size=500

# Export documents as JSONL
php bin/console micka17:typesense:documents:export products
php bin/console micka17:typesense:documents:export products --output=/tmp/export.jsonl --filter-by="stock:>0"

# Update Typesense server config dynamically
php bin/console micka17:typesense:config:update cache-num-entries=1000

# Migrate V1 config → V2 YAML (dry-run, generates output)
php bin/console micka17:typesense:migrate-config
php bin/console micka17:typesense:migrate-config --output=config/packages/typesense_v2.yaml
```

### Schema diff / ALTER

[](#schema-diff--alter)

Compare the live Typesense collection schema against the PHP attribute schema without recreating the collection.

```
# Show what would change
php bin/console micka17:typesense:schema:diff "App\Entity\Product"

# Apply non-destructive changes (add/drop fields) via PATCH
php bin/console micka17:typesense:schema:diff "App\Entity\Product" --apply

# Force full recreation when field types changed (all documents will be lost)
php bin/console micka17:typesense:schema:diff "App\Entity\Product" --force-recreate
```

The command exits with code `1` (and explains why) when the diff contains field type conflicts or collection-level metadata changes — those require `--force-recreate`.

### API Keys

[](#api-keys)

```
# List all keys (values are never shown after creation)
php bin/console micka17:typesense:keys:list

# Create a key — the value is shown ONCE, copy it immediately
php bin/console micka17:typesense:keys:create \
  --description="Search only" \
  --actions=documents:search \
  --collections=products

# Retrieve metadata for a key by ID
php bin/console micka17:typesense:keys:retrieve 42

# Delete a key (immediately revokes access)
php bin/console micka17:typesense:keys:delete 42 --yes
```

**PHP:**

```
use Micka17\TypesenseBundle\Service\KeysManager;

// Create — returns ['id' => ..., 'value' => 'secret...'] (value only at creation)
$key = $keysManager->createKey([
    'description' => 'Search only',
    'actions'     => ['documents:search'],
    'collections' => ['products'],
]);

$keysManager->listKeys();        // ['keys' => [...]]
$keysManager->retrieveKey(42);   // ['id' => 42, 'description' => ...]
$keysManager->deleteKey(42);
```

### Aliases (zero-downtime reindex)

[](#aliases-zero-downtime-reindex)

```
# Create or update an alias
php bin/console micka17:typesense:aliases:upsert products products_v2

# List all aliases
php bin/console micka17:typesense:aliases:list

# Delete an alias
php bin/console micka17:typesense:aliases:delete products
```

**PHP — atomic swap:**

```
use Micka17\TypesenseBundle\Service\AliasManager;

$previous = $aliasManager->swapAlias('products', 'products_v2');
// $previous = 'products_v1' — the old collection, safe to delete
```

> See [docs/guides/01-zero-downtime-reindex.md](docs/guides/01-zero-downtime-reindex.md) for the full workflow.

### Resources

[](#resources)

```
php bin/console micka17:typesense:synonym-sets:apply
php bin/console micka17:typesense:presets:apply
php bin/console micka17:typesense:stemming:apply
php bin/console micka17:typesense:analytics:rules:apply
php bin/console micka17:typesense:nl-search-models:apply
php bin/console micka17:typesense:conversation-models:apply
php bin/console micka17:typesense:curation-sets:apply

# List resources
php bin/console micka17:typesense:presets:list
php bin/console micka17:typesense:analytics:rules:list
# … (one :list command per resource type)

# Delete a resource
php bin/console micka17:typesense:presets:delete my-preset
php bin/console micka17:typesense:stemming:delete fr
# … (one :delete command per resource type)

# Import a stemming dictionary from file (.json or .csv)
php bin/console micka17:typesense:stemming:import fr-verbs /path/to/dict.csv
```

---

Admin Dashboard
---------------

[](#admin-dashboard)

The bundle ships with a Bootstrap 5 admin dashboard (no Webpack required) accessible at `/admin/typesense/`.

It covers all resource types: Collections, Presets, Synonym Sets, Curation Sets, Stemming Dictionaries, Analytics Rules, NL Search Models, Conversation Models.

To enable it, ensure the bundle routes are imported:

```
# config/routes.yaml
typesense_admin:
    resource: '@TypesenseBundle/config/routes.yaml'
```

Protect the prefix in your firewall as needed:

```
# config/packages/security.yaml
access_control:
    - { path: ^/admin/typesense, roles: ROLE_ADMIN }
```

---

Async Indexing (Symfony Messenger)
----------------------------------

[](#async-indexing-symfony-messenger)

By default, Doctrine events (persist/update/remove) index documents **synchronously** in the same request. For large documents or high-traffic apps, switch to async mode using [Symfony Messenger](https://symfony.com/doc/current/messenger.html).

### 1. Enable async mode

[](#1-enable-async-mode)

```
# config/packages/typesense.yaml
typesense:
    auto_update:
        enabled: true
        mode: async   # dispatches IndexDocumentMessage / DeleteDocumentMessage
```

### 2. Route the messages to a transport

[](#2-route-the-messages-to-a-transport)

```
# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async: '%env(MESSENGER_TRANSPORT_DSN)%'
        routing:
            Micka17\TypesenseBundle\Messenger\IndexDocumentMessage: async
            Micka17\TypesenseBundle\Messenger\DeleteDocumentMessage: async
```

### 3. Run the worker

[](#3-run-the-worker)

```
php bin/console messenger:consume async --time-limit=3600
```

### How it works

[](#how-it-works)

EventMessage dispatched`postPersist` / `postUpdate``IndexDocumentMessage(entityClass, entityId)``preRemove``DeleteDocumentMessage(collectionName, documentId)`The handler re-fetches the entity from the database before normalizing, so you always index the freshest data. If the entity was deleted between dispatch and handling, the handler silently skips it.

> **Note:** `auto_update: true` (the default) is equivalent to `{enabled: true, mode: sync}` — no migration needed.

---

V1 → V2 Migration Guide
-----------------------

[](#v1--v2-migration-guide)

> Run `php bin/console micka17:typesense:migrate-config` to detect issues automatically.

### Breaking changes

[](#breaking-changes)

AreaV1V2PHP≥8.1**≥8.5**Typesense Server26–29**≥30**typesense-php^5.0**^6.0**Global synonyms`typesense.synonyms``typesense.synonym_sets`Client property`$client->synonyms``$client->synonymSets``Result`getters (`getFound()`, `getHits()`)**public readonly properties** (`$result->found`, `$result->hits`)`Paginator`getters (`getLastPage()`)**virtual property hooks** (`$paginator->lastPage`)### Step 1 — Upgrade PHP and dependencies

[](#step-1--upgrade-php-and-dependencies)

```
# Requires PHP 8.5+
composer require micka-17/typesense-bundle:^2.0
```

### Step 2 — Migrate global synonyms

[](#step-2--migrate-global-synonyms)

**Before (V1):**

```
typesense:
    synonyms:
        - { id: size-synonyms, synonyms: [large, big, huge] }
```

**After (V2):**

```
typesense:
    synonym_sets:
        my-global-set:
            items:
                size-synonyms:
                    synonyms: [large, big, huge]
```

### Step 3 — Update API key scopes

[](#step-3--update-api-key-scopes)

If your Typesense API keys include `synonyms:*`, replace with `synonym_sets:*`.

### Step 4 — Update Result / Paginator usage

[](#step-4--update-result--paginator-usage)

```
// V1
$result->getFound();
$result->getHits();
$paginator->getLastPage();
$paginator->getItems();

// V2
$result->found;
$result->hits;
$paginator->lastPage;
$paginator->items;
```

### Step 5 — Migrate collection overrides to curation\_sets

[](#step-5--migrate-collection-overrides-to-curation_sets)

Collection-level overrides (`/collections/{name}/overrides`) still work in Typesense 30 but the new recommended approach uses global `curation_sets`. Migrate progressively:

```
typesense:
    curation_sets:
        my-set:
            items:
                promote-best:
                    rule: { query: laptop }
                    includes:
                        - { id: 'product-1', position: 1 }
```

### Step 6 — Re-sync

[](#step-6--re-sync)

```
php bin/console micka17:typesense:sync
php bin/console micka17:typesense:doctor
```

---

Advanced Guides
---------------

[](#advanced-guides)

GuideDescription[Zero-downtime reindex](docs/guides/01-zero-downtime-reindex.md)Alias workflow — swap collections without search interruption[Vector search &amp; auto-embedding](docs/guides/02-vector-search-auto-embedding.md)Built-in models, OpenAI, hybrid search, MMR diversification[Conversational RAG search](docs/guides/03-conversational-rag-search.md)Multi-turn chat with LLM-generated answers from your data[Analytics pipeline](docs/guides/04-analytics-pipeline.md)Track queries, clicks, and build a popular-searches feed[Search-as-you-type](docs/guides/05-search-as-you-type.md)Autocomplete UI with a Symfony JSON endpoint (~20 lines JS)[Search parameters reference](docs/reference/search-parameters.md)All Typesense search parameters including V30+ additions---

Contributing
------------

[](#contributing)

Pull requests are welcome. Please run the test suite and PHPStan before submitting:

```
composer test      # vendor/bin/phpunit
composer phpstan   # vendor/bin/phpstan analyse --memory-limit=512M
```

All tests must pass and PHPStan level 6 must report 0 errors.

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance89

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity59

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 ~51 days

Recently: every ~0 days

Total

7

Last Release

56d ago

Major Versions

v1.0.0-alpha.1 → v2.0.02026-05-07

PHP version history (2 changes)v1.0.0-alpha.1PHP &gt;=8.4

v2.0.0PHP &gt;=8.5

### Community

Maintainers

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

---

Top Contributors

[![Micka-17](https://avatars.githubusercontent.com/u/82646627?v=4)](https://github.com/Micka-17 "Micka-17 (2 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/micka-17-typesense-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/micka-17-typesense-bundle/health.svg)](https://phpackages.com/packages/micka-17-typesense-bundle)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M736](/packages/sylius-sylius)[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M388](/packages/easycorp-easyadmin-bundle)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9421.6k61](/packages/open-dxp-opendxp)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1616.4k14](/packages/2lenet-crudit-bundle)

PHPackages © 2026

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