Laravel
Guides, references, and examples to build with datalumo
Laravel Package
The Datalumo Laravel package provides a Scout-inspired integration for syncing Eloquent models to Datalumo collections. Models are automatically indexed when created, updated, or deleted — and can be searched with a fluent API.
Requirements
- PHP 8.2 or later
- Laravel 11, 12, or 13
- A Datalumo account with an API token
Installation
Install via Composer:
composer require datalumo/laravel
Publish the configuration file:
php artisan vendor:publish --tag=datalumo-config
Add your API token to .env:
DATALUMO_TOKEN=your-api-token
You can find your API token in your organisation settings.
Configuration
The published config file (config/datalumo.php) includes the following options:
| Option | Default | Description |
|---|---|---|
token |
env('DATALUMO_TOKEN') |
Your API token |
url |
https://datalumo.app |
API base URL |
queue |
true |
Queue indexing operations |
queue_connection |
null |
Queue connection name |
queue_name |
null |
Queue name |
chunk_size |
50 |
Records per chunk when importing |
Making models searchable
Add the Searchable trait to any Eloquent model and implement the toSearchableText() method:
use Datalumo\Laravel\Searchable;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use Searchable;
protected string $datalumoCollectionId = 'your-collection-uuid';
protected string $datalumoIntegrationId = 'your-integration-uuid';
public function toSearchableText(): string
{
return $this->title . "\n\n" . $this->body;
}
}
The model requires two properties:
$datalumoCollectionId— the collection where entries are synced to (used for indexing)$datalumoIntegrationId— the integration used for search, summarise, and chat
The toSearchableText() method defines the content that gets indexed.
Customising indexed data
Override these optional methods to enrich your entries with additional data:
class Article extends Model
{
use Searchable;
protected string $datalumoCollectionId = 'your-collection-uuid';
protected string $datalumoIntegrationId = 'your-integration-uuid';
public function toSearchableText(): string
{
return $this->title . "\n\n" . $this->body;
}
public function toSearchableTitle(): ?string
{
return $this->title;
}
public function toSearchableMeta(): ?array
{
return [
'author' => $this->author->name,
'published_at' => $this->published_at->toIso8601String(),
'tags' => $this->tags->pluck('name')->all(),
];
}
public function toSearchableSourceUrl(): ?string
{
return route('articles.show', $this);
}
}
All of these methods return null by default and are optional.
Conditional indexing
Control which models get indexed by overriding shouldBeSearchable():
public function shouldBeSearchable(): bool
{
return $this->is_published;
}
When a model returns false, it is automatically removed from Datalumo if it was previously indexed. This means toggling a model from published to draft will remove it from search results.
Custom source type and key
By default, the database table name is used as the source_type and the primary key as the identifier. You can override both:
public function searchableSourceType(): string
{
return 'blog_posts';
}
public function getScoutKey(): mixed
{
return $this->uuid;
}
public function getScoutKeyName(): string
{
return 'uuid';
}
Automatic syncing
Once a model uses the Searchable trait, it automatically syncs to Datalumo on every model event:
- Create — the model is indexed
- Update — the model is re-indexed with new content
- Delete — the model is removed from Datalumo
- Soft delete — respects
shouldBeSearchable()(removed if it returnsfalse) - Restore — the model is re-indexed
By default, all sync operations are dispatched to the queue. Set DATALUMO_QUEUE=false in your .env for synchronous indexing during development.
Manual syncing
You can also trigger indexing manually:
// Index a single model
$article->searchable();
// Remove a single model
$article->unsearchable();
// Index a collection of models
Article::where('is_published', true)->get()->searchable();
// Remove a collection of models
Article::where('is_draft', true)->get()->unsearchable();
Searching
Basic search
$articles = Article::search('how do refunds work')->get();
This performs a semantic search via your Datalumo integration and returns an Eloquent Collection of matching Article models, hydrated from your database. The integration can search across multiple collections at once.
Pagination
$articles = Article::search('refund policy')->paginate(15);
Returns a standard Laravel LengthAwarePaginator, fully compatible with Blade views and API responses.
Similarity threshold
Control how strict the matching is. A value of 0 matches everything, 1 requires near-exact matches:
$articles = Article::search('refund policy')
->threshold(0.4)
->get();
The default threshold is 0.25.
Filter by metadata
Narrow results to entries matching specific metadata values:
$articles = Article::search('refund policy')
->meta(['category' => 'billing'])
->get();
// Multiple filters (AND logic)
$articles = Article::search('refund policy')
->meta(['category' => 'billing', 'author' => 'John'])
->get();
Chaining
All builder methods are fluent and can be chained:
$articles = Article::search('return policy')
->threshold(0.3)
->meta(['category' => 'billing'])
->paginate(10);
Raw results
Get the raw Datalumo Entry objects without mapping to Eloquent models:
$entries = Article::search('refund policy')->raw();
foreach ($entries as $entry) {
echo $entry->title;
echo $entry->rawText;
echo $entry->sourceId;
}
This is useful when you need the full entry data or don't need to hydrate models.
AI features
Summarise
Get an AI-generated summary of search results:
$summary = Article::search('explain the refund policy')->summarise();
echo $summary->summary; // markdown summary
echo $summary->references; // source references
Control the output format and language:
$summary = Article::search('explain the refund policy')
->summarise(format: 'html', locale: 'nl');
Chat
Have a conversation grounded in your collection's content:
$response = Article::search('What is your refund policy?')->chat();
echo $response->message; // AI response
echo $response->conversationId; // use to continue
Continue an existing conversation:
$followUp = Article::search('How long do I have?')
->chat($response->conversationId);
The AI generates answers based on the content in your integration's connected collections. If the answer isn't in your content, it will say so rather than making something up.
Streaming
The summarise and chat methods also have streaming variants that return text chunks as they are generated. This is useful for building real-time chat interfaces.
Stream a chat response
$stream = Article::search('What is your refund policy?')->streamChat();
foreach ($stream->text() as $chunk) {
echo $chunk; // "Refunds", " are", " available", ...
flush();
}
Stream a summary
$stream = Article::search('explain the refund policy')
->streamSummarise(format: 'html');
foreach ($stream->text() as $chunk) {
echo $chunk;
flush();
}
Get the full text
If you want streaming transport but still need the complete response:
$stream = Article::search('hello')->streamChat();
$fullResponse = $stream->fullText();
$conversationId = $stream->conversationId();
Continue a streamed conversation
$stream = Article::search('What is your refund policy?')->streamChat();
$text = $stream->fullText();
$conversationId = $stream->conversationId();
// Continue with the conversation ID
$followUp = Article::search('How long do I have?')
->streamChat($conversationId);
Laravel HTTP streaming
Combine with Laravel's response()->stream() for real-time responses:
Route::get('/chat', function () {
$stream = Article::search(request('message'))->streamChat();
return response()->stream(function () use ($stream) {
foreach ($stream->text() as $chunk) {
echo $chunk;
ob_flush();
flush();
}
}, 200, ['Content-Type' => 'text/plain']);
});
Access raw stream events
For full control over the stream, iterate over StreamEvent objects:
$stream = Article::search('hello')->streamChat();
foreach ($stream as $event) {
if ($event->isTextDelta()) {
echo $event->data;
} elseif ($event->isCitation()) {
// handle citation: $event->citation()
}
}
Artisan commands
Import
Bulk import all existing models into Datalumo:
php artisan datalumo:import "App\Models\Article"
This processes models in chunks (default 50, configurable via datalumo.chunk_size in your config) and respects shouldBeSearchable(). Models that return false are skipped.
Flush
Remove all models of a given type from Datalumo:
php artisan datalumo:flush "App\Models\Article"
This will ask for confirmation before proceeding.
Queue configuration
By default, all indexing operations are dispatched to the queue so your application stays responsive. Configure the connection and queue name via environment variables:
DATALUMO_QUEUE=true
DATALUMO_QUEUE_CONNECTION=redis
DATALUMO_QUEUE_NAME=indexing
Set DATALUMO_QUEUE=false for synchronous indexing. This is useful during development or when you need entries to be immediately searchable.
Testing
In your application tests, mock the Engine to prevent actual API calls:
use Datalumo\Laravel\Engine;
$engine = Mockery::mock(Engine::class);
$engine->shouldReceive('update')->andReturnNull();
$engine->shouldReceive('delete')->andReturnNull();
$this->app->instance(Engine::class, $engine);
This lets you test model creation and updates without hitting the Datalumo API.
Blade components
The package includes Blade components for embedding Datalumo widgets directly in your views. No JavaScript setup needed.
Chatbot
Add a floating chatbot widget to any page:
<x-datalumo::chatbot id="your-integration-id" />
Place this before the closing </body> tag or in your layout. The chatbot appears as a floating button that opens a chat window when clicked.
Search box
Embed an inline search box with results:
<x-datalumo::search id="your-integration-id" />
To render results in a specific container, use the target attribute:
<div id="search-results"></div>
<x-datalumo::search id="your-integration-id" target="#search-results" />
Bind to your own form
Connect the search widget to an existing form instead of rendering its own:
<form id="my-search-form">
<input type="text" id="my-search-input" placeholder="Search...">
<button type="submit">Search</button>
</form>
<div id="results"></div>
<x-datalumo::search
id="your-integration-id"
form="#my-search-form"
input="#my-search-input"
target="#results"
/>
Search modal
Add a search modal that opens with Ctrl+K (or Cmd+K on Mac):
<x-datalumo::search-modal id="your-integration-id" />
Publishing views
To customise the component templates, publish them to your application:
php artisan vendor:publish --tag=datalumo-views
The views will be published to resources/views/vendor/datalumo/.
Further reading
- PHP SDK documentation — for direct API access without the Laravel integration
- API Reference — full endpoint documentation
- Getting Started — creating collections and entries from the dashboard