brynj-digital / laravel-model-vectorize
Standalone Laravel package for Cloudflare Vectorize semantic search
Package info
github.com/brynj-digital/laravel-model-vectorize
pkg:composer/brynj-digital/laravel-model-vectorize
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-04-24 08:45:48 UTC
README
A Laravel package for semantic search of models with Cloudflare Vectorize.
Features
- Semantic Search: Search by meaning, not just keywords
- Auto-Sync: Automatic indexing via Eloquent observers
- Queue Support: Background processing for better performance
- Multiple Models: Support for searching across different Eloquent models
- Cloudflare Workers AI: Automatic embedding generation
- Simple API: Clean, Laravel-idiomatic interface
Requirements
- PHP 8.1 or higher
- Laravel 10.x, 11.x, or 12.x
- A Cloudflare account with Vectorize enabled
- Cloudflare API token with Vectorize permissions
Installation
Install the package via Composer:
composer require brynj-digital/laravel-model-vectorize
Publish the configuration file:
php artisan vendor:publish --tag=vectorize-config
Configuration
1. Create a Vectorize Index
Use the provided artisan command to create a Vectorize index:
# Recommended: Using artisan command php artisan vectorize:create-index my-index # Or with custom dimensions and metric php artisan vectorize:create-index my-index --dimensions=1024 --metric=euclidean --embedding-model=@cf/baai/bge-large-en-v1.5
Alternative: Using Wrangler CLI
npx wrangler vectorize create my-index --dimensions=768 --metric=cosine
The dimensions must match your chosen embedding model:
@cf/baai/bge-small-en-v1.5: 384 dimensions@cf/baai/bge-base-en-v1.5: 768 dimensions (default)@cf/baai/bge-large-en-v1.5: 1024 dimensions
2. Create Metadata Indexes
Create metadata indexes to enable efficient filtering using the artisan commands:
# Required: Create metadata index for model filtering
php artisan vectorize:create-metadata-index model string --index-name=my-index
Note: Recent versions of this package no longer require a key metadata index, as model keys are now extracted directly from the vector ID format. This provides cleaner metadata and reduced storage requirements.
Optional: Additional Metadata Indexes for where() Clauses
You can create additional metadata indexes for any custom fields you want to filter on using the where() method:
# Example: Create index for filtering by status php artisan vectorize:create-metadata-index status string --index-name=my-index # Example: Create index for filtering by category_id php artisan vectorize:create-metadata-index category_id number --index-name=my-index # Example: Create index for boolean fields php artisan vectorize:create-metadata-index in_stock boolean --index-name=my-index
Alternative: Using Wrangler CLI
# Required: Create metadata index for model filtering npx wrangler vectorize create-metadata-index my-index --property-name=model --type=string # Optional: Additional metadata indexes npx wrangler vectorize create-metadata-index my-index --property-name=status --type=string npx wrangler vectorize create-metadata-index my-index --property-name=category_id --type=number npx wrangler vectorize create-metadata-index my-index --property-name=in_stock --type=boolean
Managing Metadata Indexes
Use the provided commands to manage your metadata indexes:
# List all metadata indexes for an index php artisan vectorize:list-metadata-indexes --index-name=my-index # Delete a metadata index php artisan vectorize:delete-metadata-index status --index-name=my-index
3. Environment Variables
Add the following to your .env file:
CLOUDFLARE_ACCOUNT_ID=your_account_id CLOUDFLARE_API_TOKEN=your_api_token CLOUDFLARE_VECTORIZE_INDEX=my-index CLOUDFLARE_EMBEDDING_MODEL=@cf/baai/bge-base-en-v1.5 # Optional: Queue configuration VECTORIZE_QUEUE=true VECTORIZE_QUEUE_CONNECTION=redis VECTORIZE_QUEUE_NAME=vectorize
Usage
Basic Model Setup
Add the VectorSearchable trait to your model:
use BrynjDigital\LaravelModelVectorize\Traits\VectorSearchable; class Product extends Model { use VectorSearchable; /** * Get the indexable data array for the model. */ public function toSearchableArray(): array { return [ 'name' => $this->name, 'description' => $this->description, 'brand' => $this->brand, 'category' => $this->category, ]; } }
Custom Text Conversion (Optional)
For more control over how your model is converted to searchable text, implement a toSearchableText() method:
class Product extends Model { use VectorSearchable; /** * Convert the model to searchable text. * This method takes precedence over toSearchableArray(). */ public function toSearchableText(): string { return "{$this->name}. {$this->brand}. {$this->description}"; } public function toSearchableArray(): array { return [ 'name' => $this->name, 'description' => $this->description, 'brand' => $this->brand, ]; } }
Searching
// Simple search $products = Product::vectorSearch('wireless headphones')->get(); // With limit $products = Product::vectorSearch('laptop')->take(20)->get(); // With filters (metadata-based) $products = Product::vectorSearch('gaming laptop') ->where('status', 'published') ->where('in_stock', true) ->get(); // Get raw results with scores $results = Product::vectorSearch('laptop')->raw(); // Returns: [['id' => ..., 'score' => 0.95, 'metadata' => [...]], ...] // Lazy loading $products = Product::vectorSearch('tablet')->cursor()->each(function ($product) { // Process each result }); // First result only $product = Product::vectorSearch('macbook pro')->first(); // Custom callback $products = Product::vectorSearch('laptop', function($client, $query) { return $client->search($query, 50, ['custom' => 'filter']); })->get();
Manual Syncing
// Sync single model $product->syncToVectorize(); // Remove from index $product->removeFromVectorize(); // Bulk operations Product::makeAllSearchableInVectorize(); Product::removeAllFromVectorize();
Artisan Commands
Vectorize Index Management
# Create a new Vectorize index php artisan vectorize:create-index # Create index with custom dimensions and metric php artisan vectorize:create-index my-index --dimensions=1024 --metric=euclidean --embedding-model=@cf/baai/bge-large-en-v1.5 # Drop (delete) a Vectorize index php artisan vectorize:drop-index my-index # Force drop without confirmation (use with caution) php artisan vectorize:drop-index my-index --force
Options for vectorize:create-index:
name(optional): Index name (uses config value if not provided)--dimensions: Vector dimensions (default: 768)--metric: Distance metric - cosine, euclidean, or dotproduct (default: cosine)--embedding-model: Cloudflare embedding model (default: @cf/baai/bge-base-en-v1.5)
Options for vectorize:drop-index:
name(optional): Index name (uses config value if not provided)--force: Skip confirmation prompts
Metadata Index Management
# Create a metadata index for filtering php artisan vectorize:create-metadata-index property-name type --index-name=my-index # List all metadata indexes php artisan vectorize:list-metadata-indexes --index-name=my-index # Delete a metadata index php artisan vectorize:delete-metadata-index property-name --index-name=my-index # Force delete without confirmation php artisan vectorize:delete-metadata-index property-name --index-name=my-index --force
Arguments for vectorize:create-metadata-index:
property-name: The metadata property to indextype: Property type (string, number, boolean)
Arguments for vectorize:delete-metadata-index:
property-name: The metadata property to delete
Options for metadata index commands:
--index-name: Vectorize index name (uses config value if not provided)--force: Skip confirmation prompts (delete command only)
Model Import/Export Commands
# Import all products php artisan vectorize:import "App\Models\Product" # Flush all products php artisan vectorize:flush "App\Models\Product" # Display index info php artisan vectorize:info
Model Observers
The package automatically syncs your models when you create, update, or delete them:
// Automatically indexed $product = Product::create([ 'name' => 'Wireless Headphones', 'description' => 'High-quality Bluetooth headphones', ]); // Automatically re-indexed $product->update(['name' => 'Premium Wireless Headphones']); // Automatically removed from index $product->delete();
Customizing Sync Behavior
class Product extends Model { use VectorSearchable; /** * Determine if this model should be searchable. */ public function shouldBeSearchable(): bool { return $this->status === 'published'; } /** * Disable auto-sync for this model. */ public function syncToVectorizeAutomatically(): bool { return false; } }
Configuration Reference
// config/vectorize.php return [ // Cloudflare credentials 'cloudflare' => [ 'account_id' => env('CLOUDFLARE_ACCOUNT_ID'), 'api_token' => env('CLOUDFLARE_API_TOKEN'), ], // Index configuration 'index' => env('CLOUDFLARE_VECTORIZE_INDEX', 'default'), 'embedding_model' => env('CLOUDFLARE_EMBEDDING_MODEL', '@cf/baai/bge-base-en-v1.5'), // Synchronization settings 'sync' => [ 'automatically' => env('VECTORIZE_AUTO_SYNC', true), ], // Queue configuration 'queue' => [ 'enabled' => env('VECTORIZE_QUEUE', false), 'connection' => env('VECTORIZE_QUEUE_CONNECTION', null), 'queue' => env('VECTORIZE_QUEUE_NAME', 'default'), ], // Batch processing 'batch_size' => env('VECTORIZE_BATCH_SIZE', 100), // API settings 'timeout' => env('VECTORIZE_TIMEOUT', 30), ];
How It Works
-
Indexing: When a model is indexed:
- Calls
toSearchableText()or flattenstoSearchableArray()to text - Generates an embedding using Cloudflare Workers AI
- Stores the vector in Cloudflare Vectorize with metadata
- Calls
-
Searching: When you search:
- Your query text is converted to an embedding
- Vectorize finds the most similar vectors
- Results are mapped back to your Eloquent models
- Models are fetched from your database and returned
-
Vector IDs: The package prefixes vector IDs with the model class name to support multiple model types in one index (e.g.,
App_Models_Product_123)
Events
The package dispatches events after operations:
use BrynjDigital\LaravelModelVectorize\Events\ModelIndexed; use BrynjDigital\LaravelModelVectorize\Events\ModelRemovedFromIndex; // Listen to events Event::listen(ModelIndexed::class, function ($event) { // $event->model // $event->vectorData }); Event::listen(ModelRemovedFromIndex::class, function ($event) { // $event->model // $event->vectorizeId });
Best Practices
Optimizing Search Quality
-
Use descriptive text: Include context in your searchable content
public function toSearchableText(): string { return "Product: {$this->name}. Brand: {$this->brand}. {$this->description}"; }
-
Avoid overly long text: Embeddings work best with focused, relevant content
-
Include relevant metadata: Add fields you'll filter on to
toSearchableArray()
Performance Tips
- Enable queueing for production: Prevent blocking requests
- Use batch operations: Import in bulk with artisan commands
- Limit search results: Only fetch what you need with
take() - Cache frequent queries: Use Laravel's cache for popular searches
License
This package is open-source software licensed under the MIT license.
Support
For issues, questions, or contributions, please visit the GitHub repository.