PHPackages                             tag1/scolta-laravel - 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. [Search &amp; Filtering](/categories/search)
4. /
5. tag1/scolta-laravel

ActiveLibrary[Search &amp; Filtering](/categories/search)

tag1/scolta-laravel
===================

Scolta AI Search for Laravel — zero-infrastructure AI-powered search with Pagefind

1.0.1(1w ago)20[2 PRs](https://github.com/tag1consulting/scolta-laravel/pulls)MITPHPPHP &gt;=8.1CI passing

Since Apr 8Pushed 6d agoCompare

[ Source](https://github.com/tag1consulting/scolta-laravel)[ Packagist](https://packagist.org/packages/tag1/scolta-laravel)[ RSS](/packages/tag1-scolta-laravel/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (9)Versions (41)Used By (0)

Scolta for Laravel
==================

[](#scolta-for-laravel)

[![CI](https://github.com/tag1consulting/scolta-laravel/actions/workflows/ci.yml/badge.svg)](https://github.com/tag1consulting/scolta-laravel/actions/workflows/ci.yml)

Built and maintained by [Tag1 Consulting](https://www.tag1.com/) — technology leadership since 2007.

Laravel 11/12/13 package — Artisan commands, `Searchable` trait for Eloquent models, and AI-powered search built on Pagefind.

Status
------

[](#status)

Scolta 1.0 — the API documented here is stable. Breaking changes follow semantic versioning: no removal or signature change without a major version bump and a deprecation cycle. File bugs at the repo issue tracker.

What Is Scolta?
---------------

[](#what-is-scolta)

Scolta is a scoring, ranking, and AI layer built on [Pagefind](https://pagefind.app/). Pagefind is the search engine: it builds a static inverted index at publish time, runs a browser-side WASM search engine, produces word-position data, and generates highlighted excerpts. Scolta takes Pagefind's result set and re-ranks it with configurable boosts — title match weight, content match weight, recency decay curves, and phrase-proximity multipliers. No search server required. Queries resolve in the visitor's browser against a pre-built static index.

This package is the Laravel adapter. It provides Artisan commands for building and maintaining the index, a `Searchable` trait for Eloquent models, a `` Blade component, change tracking via an observer pattern, and REST API endpoints for the AI features. The actual scoring, indexing logic, memory management, and AI communication live in [scolta-php](https://github.com/tag1consulting/scolta-php), which this package depends on. Scoring runs client-side via the `scolta.js` browser asset and the pre-built WASM module shipped with scolta-php.

The LLM tier — query expansion, result summarization, follow-up questions — is optional. When enabled, it sends the query text and selected result excerpts to a configured LLM provider (Anthropic, OpenAI, or a self-hosted Ollama endpoint). The base search tier shares nothing with any third party.

Running Example
---------------

[](#running-example)

The examples in this README and the other Scolta repos use a recipe catalog as the concrete data set. Recipes are a good showcase because recipe vocabulary has genuine cross-dialect mismatches:

- A search for `aubergine parmesan` should surface *Eggplant Parmigiana*.
- A search for `chinese noodle soup` should surface *Lanzhou Beef Noodles*, *Wonton Soup*, and *Dan Dan Noodles*.
- A search for `gluten free pasta` should surface *Zucchini Spaghetti with Pesto* and *Rice Noodle Stir-Fry*.
- A search for `quick dinner under 30 min` should surface *Pad Kra Pao*, *Dan Dan Noodles*, and *Steak Frites*.

Here is how to model and index the recipe catalog in Laravel:

```
// app/Models/Recipe.php
use Tag1\Scolta\Export\ContentItem;
use Tag1\ScoltaLaravel\Searchable;

class Recipe extends Model
{
    use Searchable;

    public function toSearchableContent(): ContentItem
    {
        return new ContentItem(
            id:       "recipe-{$this->id}",
            title:    $this->name,
            bodyHtml: "{$this->description}"
                    . "Ingredients"
                    . implode('', array_map(fn($i) => "{$i}", $this->ingredients))
                    . "Tags: {$this->tags}, {$this->regional_synonyms}",
            url:      "/recipes/{$this->slug}",
            date:     $this->updated_at->format('Y-m-d'),
            siteName: config('scolta.site_name', config('app.name')),
        );
    }

    public function scopeSearchable($query)
    {
        return $query->where('published', true);
    }
}
```

Register the model in `config/scolta.php`:

```
'models' => [App\Models\Recipe::class],
```

Build the index:

```
php artisan scolta:build
```

Then add `` to any Blade template and visit the page. A search for `aubergine parmesan` surfaces *Eggplant Parmigiana* because the body HTML includes both the American term "eggplant" and the Italian name. Scolta's title boost lifts it above pages that mention aubergine only in passing.

The recipe fixture HTML files live in [scolta-php](https://github.com/tag1consulting/scolta-php) at `tests/fixtures/recipes/` if you want a pre-built data set to index without a database.

Quick Install
-------------

[](#quick-install)

```
# 1. Install
composer require tag1/scolta-laravel:^1.0 tag1/scolta-php:^1.0

# 2. Publish config, migrations, and assets
php artisan vendor:publish --tag=scolta-config --tag=scolta-migrations --tag=scolta-assets

# 3. Run migrations
php artisan migrate

# 4. Add the Searchable trait to your models and register them in config/scolta.php

# 5. Build the search index
php artisan scolta:build

# 6. Add  to any Blade template

# 7. Set your API key to unlock AI features
```

In `.env`:

```
SCOLTA_API_KEY=sk-ant-...
```

With an API key configured, search queries are automatically expanded with related terms, results include an AI summary, and visitors can ask follow-up questions.

Verify It Works
---------------

[](#verify-it-works)

```
php artisan scolta:check-setup
```

This verifies PHP version, index directories, indexer selection, AI provider configuration, and binary availability.

```
php artisan scolta:status
```

The health endpoint also reports current state: `GET /api/scolta/v1/health`

What Scolta Is Built For
------------------------

[](#what-scolta-is-built-for)

Scolta is designed for content search on Laravel applications: articles, documentation, product catalogs, knowledge bases, and other Eloquent model content indexed at build time. Laravel powers SaaS products, enterprise applications, API platforms, and content-driven sites — and Scolta is tuned for the content search needs of these applications.

The static-index architecture means no Elasticsearch or Solr server to provision. Scolta replaces hosted search SaaS (Algolia, Coveo, SearchStax) and Solr/Elasticsearch backends for Laravel applications where the search use case is full-text relevance, recency, and phrase matching. It runs on managed hosting where binary execution is restricted, using the PHP indexer automatically.

### Migrating from Laravel Scout

[](#migrating-from-laravel-scout)

Scout and Scolta solve different problems. Scout drives external search servers (Algolia, Meilisearch, Typesense). Scolta runs Pagefind, which produces a static browser-side index — no search server required. Scolta then re-ranks Pagefind's results and optionally adds an AI layer.

Replace `toSearchArray()` with `toSearchableContent()` and `scopeSearch()` with `scopeSearchable()`. Remove Scout from `composer.json`, publish Scolta's config and migrations, and replace Scout search calls with ``.

What you gain: no external search service bill, AI query expansion and summarization, works on shared and managed hosting. What you give up: Scout's per-record real-time index updates and its driver flexibility.

Memory and Scale
----------------

[](#memory-and-scale)

The default memory profile is `conservative`, which targets a peak RSS under 96 MB and works on shared hosting with a 128 MB PHP `memory_limit`. Scolta never silently upgrades to a larger profile.

The admin interface shows the detected PHP `memory_limit` and suggests a profile. The profile selection is always left to the admin.

Pass the profile via the Artisan CLI:

```
php artisan scolta:build --memory-budget=balanced
```

Available profiles: `conservative` (default, ≤96 MB), `balanced` (≤200 MB), `aggressive` (≤384 MB). Higher budget means fewer, larger index chunks and faster builds.

Or set it in `.env`:

```
SCOLTA_MEMORY_BUDGET=balanced
```

Tested ceiling at the `conservative` profile: 50,000 pages. Higher counts likely work; not certified yet.

Deploying to PaaS Platforms
---------------------------

[](#deploying-to-paas-platforms)

On PaaS platforms — including Laravel Cloud, Forge with push-to-deploy, Vapor, Railway, and Render — the filesystem is rebuilt from your repository on every deploy. Any files written outside the repo at install time are wiped, including assets published by `vendor:publish`.

`php artisan vendor:publish --tag=scolta-assets` must run as part of your **build pipeline**, not just during initial setup.

### Wiring it automatically via `post-autoload-dump`

[](#wiring-it-automatically-via-post-autoload-dump)

Add it to your **application's** `composer.json` scripts:

```
"scripts": {
    "post-autoload-dump": [
        "@php artisan package:discover --ansi",
        "@php artisan vendor:publish --tag=scolta-assets --force --ansi"
    ]
}
```

Composer runs `post-autoload-dump` on every `composer install` and `composer update`, which PaaS platforms execute automatically on each deploy. The `--force` flag is required so assets are refreshed even when the destination directory already exists from a previous build cache.

> **Important:** Composer only runs scripts from the **root package** — your application. Scripts in a dependency's `composer.json` (including Scolta's own) are never executed for consumers. You must add the script to your own `composer.json`.

### Platform-specific steps

[](#platform-specific-steps)

**Laravel Cloud**: Runs `composer install` on each deployment. The `post-autoload-dump` script above runs automatically.

**Laravel Vapor**: Runs `composer install` in the Lambda package build step. The `post-autoload-dump` script above runs automatically.

**Laravel Forge (push-to-deploy)**: Add the publish command to your Forge deployment script, after the `composer install` line:

```
php artisan vendor:publish --tag=scolta-assets --force
```

AI Features and Privacy
-----------------------

[](#ai-features-and-privacy)

Scolta's AI tier is optional. When enabled:

- The LLM receives: the query text, and the titles and excerpts of the top N results (default: 10, configurable via `ai_summary_top_n`).
- The LLM does not receive: the full index contents, full page text, user session data, or visitor identity.
- Which provider receives the query data depends on your `SCOLTA_AI_PROVIDER` setting: `anthropic`, `openai`, or a self-hosted endpoint via `SCOLTA_AI_BASE_URL`.

The base search tier — Pagefind index lookup and Scolta WASM scoring — runs entirely in the visitor's browser with no server-side involvement beyond serving static index files.

Configuration
-------------

[](#configuration)

All settings live in `config/scolta.php` with `.env` overrides. After editing `config/scolta.php`, run `php artisan config:clear`.

### AI Provider

[](#ai-provider)

Setting`.env` key`config/scolta.php` keyDefaultDescriptionProvider`SCOLTA_AI_PROVIDER``ai_provider``anthropic``anthropic` or `openai`API key`SCOLTA_API_KEY``ai_api_key`—Authentication for AI featuresModel`SCOLTA_AI_MODEL``ai_model``claude-sonnet-4-5-20250929`LLM model identifierExpansion model`SCOLTA_EXPANSION_MODEL``ai_expansion_model``''` (same as `ai_model`)Optional separate model for query expansion. When set, `expand-query` uses this model while `summarize` and `followup` use `ai_model`. Empty means all AI operations use `ai_model`.Base URL`SCOLTA_AI_BASE_URL``ai_base_url`provider defaultCustom endpoint for proxies or Azure OpenAIQuery expansion`SCOLTA_AI_EXPAND``ai_expand_query``true`Toggle AI query expansion on/offSummarization`SCOLTA_AI_SUMMARIZE``ai_summarize``true`Toggle AI result summarization on/offSummary top N—`ai_summary_top_n``10`How many top results to send to AI for summarizationSummary max chars—`ai_summary_max_chars``4000`Max content characters sent to AI per requestMax follow-ups`SCOLTA_MAX_FOLLOWUPS``max_follow_ups``3`Follow-up questions allowed per sessionAI languages`SCOLTA_AI_LANGUAGES``ai_languages``['en']`Languages the AI responds in (matches user query language)In `.env`:

```
SCOLTA_AI_PROVIDER=anthropic
SCOLTA_API_KEY=sk-ant-...
SCOLTA_AI_MODEL=claude-sonnet-4-5-20250929
SCOLTA_AI_EXPAND=true
SCOLTA_AI_SUMMARIZE=true
```

For multilingual sites:

```
// config/scolta.php
'ai_languages' => ['en', 'fr', 'de'],
```

### Search Scoring

[](#search-scoring)

Scoring settings live under the `scoring` key in `config/scolta.php`.

Setting`.env` key`config/scolta.php` pathDefaultDescriptionTitle match boost—`scoring.title_match_boost``1.0`Boost when query terms appear in the titleTitle all-terms multiplier—`scoring.title_all_terms_multiplier``1.5`Extra multiplier when ALL terms match the titleContent match boost—`scoring.content_match_boost``0.4`Boost for query term matches in body/excerptExpand primary weight—`scoring.expand_primary_weight``0.5`Weight for original query results vs AI-expanded results (higher = original query dominates; raise to 0.7+ if you want literal keyword matches to win)Recency strategy`SCOLTA_RECENCY_STRATEGY``scoring.recency_strategy``exponential``exponential`, `linear`, `step`, `none`, or `custom`Recency boost max—`scoring.recency_boost_max``0.5`Maximum positive boost for very recent contentRecency half-life days—`scoring.recency_half_life_days``365`Days until recency boost halvesRecency penalty after days—`scoring.recency_penalty_after_days``1825`Age before content gets a penalty (~5 years)Recency max penalty—`scoring.recency_max_penalty``0.3`Maximum negative penalty for very old contentLanguage`SCOLTA_LANGUAGE``scoring.language``en`ISO 639-1 code for stop word filteringCustom stop words—`scoring.custom_stop_words``[]`Extra stop words beyond the language's built-in list**News site** (recency matters a lot):

```
// config/scolta.php
'scoring' => [
    'recency_boost_max'           => 0.8,
    'recency_half_life_days'      => 30,
    'recency_penalty_after_days'  => 365,
    'recency_max_penalty'         => 0.5,
],
```

**Documentation site** (recency doesn't matter, titles matter a lot):

```
'scoring' => [
    'recency_strategy'           => 'none',
    'title_match_boost'          => 2.0,
    'title_all_terms_multiplier' => 2.5,
],
```

**Recipe catalog** (no recency, title precision matters):

```
'scoring' => [
    'recency_strategy'           => 'none',
    'title_match_boost'          => 1.5,
    'title_all_terms_multiplier' => 2.0,
],
```

### Display

[](#display)

Display settings are top-level keys in `config/scolta.php`.

Setting`config/scolta.php` keyDefaultDescriptionExcerpt length`excerpt_length``300`Characters shown in result excerptsResults per page`results_per_page``10`Results shown per pageMax Pagefind results`max_pagefind_results``50`Total results fetched from index before scoringShow attribution`show_attribution``false`Render "Powered by Scolta" below the search widget### Site Identity

[](#site-identity)

Setting`.env` key`config/scolta.php` keyDefaultDescriptionSite name`SCOLTA_SITE_NAME``site_name`app nameIncluded in AI prompts so the AI knows what site it's searchingSite description—`site_description``website`Brief description for AI context### Custom Prompts

[](#custom-prompts)

Override prompts via the top-level keys `prompt_expand_query`, `prompt_summarize`, and `prompt_follow_up` in `config/scolta.php`, or use an event listener:

```
// app/Listeners/EnrichScoltaPrompt.php
use Tag1\ScoltaLaravel\Events\PromptEnrichEvent;

class EnrichScoltaPrompt
{
    public function handle(PromptEnrichEvent $event): void
    {
        if ($event->promptName === 'summarize') {
            $event->resolvedPrompt .= "\n\nFocus on dietary information and cuisine type.";
        }
    }
}
```

Register in `EventServiceProvider`:

```
protected $listen = [
    \Tag1\ScoltaLaravel\Events\PromptEnrichEvent::class => [
        \App\Listeners\EnrichScoltaPrompt::class,
    ],
];
```

### Preset

[](#preset)

Set `SCOLTA_PRESET` in `.env` (or edit `config/scolta.php`) to apply a named scoring preset. Any explicit values in the `scoring` array override the preset.

PresetDescription`content_catalog`Recipe sites, wikis, content collections`reference`Documentation, knowledge bases, encyclopedias`ecommerce`Online stores, product catalogs`blog`Blogs, news, editorial content`none`No preset (default) — all values from `scoring` array```
SCOLTA_PRESET=blog
```

### Indexer and Memory

[](#indexer-and-memory)

Setting`.env` key`config/scolta.php` keyDefaultDescriptionIndexer backend`SCOLTA_INDEXER``indexer``auto``auto` (always PHP), `php`, or `binary`Memory budget`SCOLTA_MEMORY_BUDGET``memory_budget.profile``conservative``conservative`, `balanced`, or `aggressive`Chunk size`SCOLTA_CHUNK_SIZE``memory_budget.chunk_size`profile defaultPages per chunk during PHP indexer build### Pagefind

[](#pagefind)

Setting`.env` key`config/scolta.php` pathDefaultDescriptionBinary path`SCOLTA_PAGEFIND_BINARY``pagefind.binary``pagefind`Path to Pagefind CLI binaryBuild dir`SCOLTA_BUILD_DIR``pagefind.build_dir``storage/scolta/build`HTML export directory for binary pipelineOutput dir`SCOLTA_OUTPUT_DIR``pagefind.output_dir``public/scolta-pagefind`Pagefind index output directory### Caching and Rate Limiting

[](#caching-and-rate-limiting)

Setting`.env` key`config/scolta.php` keyDefaultDescriptionCache TTL`SCOLTA_CACHE_TTL``cache_ttl``2592000` (30 days)AI response cache TTL in secondsRate limit`SCOLTA_RATE_LIMIT``rate_limit``30`Max API requests per minute per IP### Routes and Middleware

[](#routes-and-middleware)

Setting`config/scolta.php` keyDefaultDescriptionRoute prefix`route_prefix``api/scolta/v1`Prefix for all Scolta API routesAPI middleware`middleware``['api']`Middleware for AI API routesHealth middleware`health_middleware``['api']`Middleware for the health check endpointAmazee route prefix`amazee_route_prefix``scolta/amazee`Prefix for Amazee.ai admin settings routesAmazee middleware`amazee_middleware``['web']`Middleware for Amazee.ai settings routes### Auto Rebuild

[](#auto-rebuild)

Setting`.env` key`config/scolta.php` keyDefaultDescriptionAuto rebuild`SCOLTA_AUTO_REBUILD``auto_rebuild``true`Dispatch rebuild to queue on content changesRebuild delay`SCOLTA_AUTO_REBUILD_DELAY``auto_rebuild_delay``300`Debounce delay in seconds### Sort and Filter Fields

[](#sort-and-filter-fields)

Setting`config/scolta.php` keyDefaultDescriptionSortable fields`sortable_fields``[]`Field names for `data-pagefind-sort` attributesSort descriptions`sortable_field_descriptions``[]`Human-readable sort field descriptions for LLMFilter fields`filter_fields``[]`Pagefind filter dimension namesFilter descriptions`filter_field_descriptions``[]`Human-readable filter descriptions for LLM### Amazee.ai Integration

[](#amazeeai-integration)

Amazee.ai provides a managed LiteLLM proxy with a free trial. Scolta auto-provisions a trial on the first AI request when no `SCOLTA_API_KEY` is configured.

**CLI provisioning:**

```
php artisan scolta:amazee:provision user@example.com
```

**Admin UI:** Visit `/scolta/amazee` (configurable via `amazee_route_prefix`) for the multi-step connection flow. Protect the route with auth middleware in production:

```
// config/scolta.php
'amazee_middleware' => ['web', 'auth'],
```

**Routes:**

MethodPathDescriptionGET`/scolta/amazee`Settings pagePOST`/scolta/amazee/trial`Start free trialPOST`/scolta/amazee/request-code`Request OTP codePOST`/scolta/amazee/verify-code`Verify OTP codeGET`/scolta/amazee/regions`List available regionsPOST`/scolta/amazee/connect`Complete connectionDELETE`/scolta/amazee/disconnect`Disconnect### Migrations

[](#migrations)

Scolta uses two database tables. Publish and run migrations during installation:

```
php artisan vendor:publish --tag=scolta-migrations
php artisan migrate
```

TableDescription`scolta_tracker`Change tracking for Eloquent models. The `ScoltaObserver` writes here when models are created, updated, or deleted. Used by incremental builds to process only changed content.`scolta_config`Key/value config store for Amazee.ai credentials and auto-configured model settings. Tokens are encrypted via Laravel's `Crypt` facade.Debugging
---------

[](#debugging)

### "Pagefind binary not found"

[](#pagefind-binary-not-found)

On managed hosting where `exec()` is disabled, the package falls back to the PHP indexer automatically. The PHP indexer works on WP Engine, Kinsta, Flywheel, Pantheon, and any host where `exec()` is unavailable. It supports 14 languages via Snowball stemming. The search experience is identical to using the binary.

```
php artisan scolta:check-setup
php artisan scolta:status
```

To install the binary on a host that supports it:

```
php artisan scolta:download-pagefind
# or: npm install -g pagefind
```

Set `SCOLTA_INDEXER=binary` in `.env` and rebuild.

### "AI features not working"

[](#ai-features-not-working)

1. Verify API key: `php artisan scolta:check-setup`
2. Clear stale cache: `php artisan scolta:clear-cache`
3. Clear config cache: `php artisan config:clear`
4. Confirm the model name in `config/scolta.php`

### "AI summary says 'I don't have enough context'"

[](#ai-summary-says-i-dont-have-enough-context)

The defaults (10 results, 4000 chars) are already tuned for curation. If still insufficient, increase further:

```
// config/scolta.php
'ai_summary_top_n'     => 15,
'ai_summary_max_chars' => 6000,
```

### "AI responses are in the wrong language"

[](#ai-responses-are-in-the-wrong-language)

Set `ai_languages` to match your site's language(s):

```
'ai_languages' => ['de'],  // or ['en', 'fr', 'de'] for multilingual
```

### "Expanded queries return irrelevant results"

[](#expanded-queries-return-irrelevant-results)

Raise `expand_primary_weight` (default: 0.5) to make original query terms dominate more, or disable expansion:

```
// config/scolta.php
'scoring' => [
    'expand_primary_weight' => 0.8,  // closer to 1.0 = original query dominates
],
// or: 'ai_expand_query' => false,
```

### "No search results"

[](#no-search-results)

1. Check index status: `php artisan scolta:status`
2. Run a full rebuild: `php artisan scolta:build`
3. Verify published assets: `php artisan vendor:publish --tag=scolta-assets --force`
4. Confirm the Pagefind output directory is web-accessible (must be under `public/`)

### "Models not being indexed"

[](#models-not-being-indexed)

Run `php artisan scolta:discover` to find `Searchable` models not registered in `config/scolta.php`. The observer only tracks models listed there.

Add the Searchable Trait
------------------------

[](#add-the-searchable-trait)

```
use Tag1\Scolta\Export\ContentItem;
use Tag1\ScoltaLaravel\Searchable;

class Article extends Model
{
    use Searchable;

    public function toSearchableContent(): ContentItem
    {
        return new ContentItem(
            id:       "article-{$this->id}",
            title:    $this->title,
            bodyHtml: $this->body,
            url:      "/articles/{$this->slug}",
            date:     $this->updated_at->format('Y-m-d'),
            siteName: config('scolta.site_name', config('app.name')),
        );
    }

    // Optional: filter which records to index
    public function scopeSearchable($query)
    {
        return $query->where('published', true);
    }
}
```

Register the model in `config/scolta.php`:

```
'models' => [
    App\Models\Article::class,
    App\Models\Page::class,
],
```

Artisan Commands
----------------

[](#artisan-commands)

```
php artisan scolta:build                    # Full build: mark all content, export, run indexer
php artisan scolta:build --incremental      # Only process tracked changes
php artisan scolta:build --skip-pagefind    # Export HTML without rebuilding index
php artisan scolta:build --memory-budget=balanced  # Use balanced memory profile
php artisan scolta:export                   # Export content to HTML only
php artisan scolta:export --incremental     # Only export tracked changes
php artisan scolta:rebuild-index            # Rebuild index from existing HTML files
php artisan scolta:status                   # Show tracker, content, index, and AI status
php artisan scolta:discover                 # Find Searchable models not yet in config
php artisan scolta:clear-cache              # Clear Scolta AI response caches
php artisan scolta:cleanup                  # Remove stale index artifacts and orphaned state files
php artisan scolta:cleanup --dry-run        # Show what would be removed without deleting
php artisan scolta:memory-budget            # Show the current memory budget profile
php artisan scolta:memory-budget --set=balanced  # Set profile: conservative, balanced, or aggressive
php artisan scolta:download-pagefind        # Download Pagefind binary for your platform
php artisan scolta:check-setup              # Verify PHP, indexer, and configuration
php artisan scolta:amazee:provision {email}  # Provision a free Amazee.ai trial account
php artisan scolta:amazee:provision {email} --force  # Provision even if a provider is already configured
```

API Endpoints
-------------

[](#api-endpoints)

MethodPathMiddlewareDescriptionPOST`/api/scolta/v1/expand-query`api, throttle:scoltaExpand a search queryPOST`/api/scolta/v1/summarize`api, throttle:scoltaSummarize search resultsPOST`/api/scolta/v1/followup`api, throttle:scoltaContinue a conversationGET`/api/scolta/v1/health`apiHealth checkGET`/api/scolta/v1/build-progress`api, auth:sanctumBuild progress statusPOST`/api/scolta/v1/rebuild-now`api, auth:sanctumDispatch a rebuild jobRoute prefix and middleware are configurable via `route_prefix` and `middleware` in `config/scolta.php`.

The `build-progress` and `rebuild-now` endpoints require Sanctum authentication and are intended for admin dashboards. The AI endpoints (`expand-query`, `summarize`, `followup`) use the configurable `middleware` array and are typically public-facing.

> **Note:** Amazee.ai admin routes (`/scolta/amazee/*`) use `web` middleware and are documented in the [Amazee.ai Integration](#amazeeai-integration) section below.

Searchable Trait API
--------------------

[](#searchable-trait-api)

MethodDefaultDescription`toSearchableContent()`column heuristicReturn a `ContentItem` for indexing`scopeSearchable($query)`all recordsFilter which records to index`getSearchableType()`class nameContent type identifier for tracking`shouldBeSearchable()``true`Whether this instance should be indexedOptional Upgrades
-----------------

[](#optional-upgrades)

### Upgrade to the Pagefind binary indexer

[](#upgrade-to-the-pagefind-binary-indexer)

On hosts with Node.js ≥ 18 or binary execution support, the Pagefind binary is 5–10× faster than the PHP indexer:

```
php artisan scolta:download-pagefind
# or: npm install -g pagefind
```

Set `SCOLTA_INDEXER=binary` in `.env` and rebuild. The PHP indexer continues to work on managed hosts (WP Engine, Kinsta, Pantheon, etc.) where binary execution is disabled.

### Keeping the Index Fresh

[](#keeping-the-index-fresh)

When **auto\_rebuild** is enabled (`SCOLTA_AUTO_REBUILD=true` in `.env`), a `ScoltaObserver` watches the models listed in `config/scolta.php` and dispatches a debounced `TriggerRebuild` job whenever a model is saved or deleted (default delay: 5 minutes). This requires a queue worker running.

Three paths are available, in order of reliability:

#### Path A: Queue worker / Supervisor (recommended)

[](#path-a-queue-worker--supervisor-recommended)

Enable **auto\_rebuild** and run a persistent queue worker:

```
php artisan queue:work --tries=3
```

For production, use [Supervisor](https://laravel.com/docs/queues#supervisor-configuration) or [Laravel Forge](https://forge.laravel.com) to keep the worker running. Forge configures this automatically.

Content saves trigger `ScoltaObserver`, which dispatches a `TriggerRebuild` job after the configured delay. The queue worker processes that job in the background.

#### Path B: Laravel Scheduler

[](#path-b-laravel-scheduler)

Add a scheduled rebuild to your app. One system cron entry handles all Laravel scheduled tasks:

```
* * * * * cd /var/www/html && php artisan schedule:run 2>&1 | logger -t scolta

```

Then schedule the build in `routes/console.php` (Laravel 11+):

```
use Illuminate\Support\Facades\Schedule;

Schedule::command('scolta:build --incremental')->everyFifteenMinutes();
```

Or in `app/Console/Kernel.php` (Laravel 10):

```
protected function schedule(Schedule $schedule): void
{
    $schedule->command('scolta:build --incremental')->everyFifteenMinutes();
}
```

`--incremental` only processes tracked changes, so runs are fast when nothing has changed.

#### Path C: System cron (direct)

[](#path-c-system-cron-direct)

Call `scolta:build` directly from system cron, bypassing the Scheduler:

```
*/15 * * * * cd /var/www/html && php artisan scolta:build --incremental 2>&1 | logger -t scolta

```

Simpler than the Scheduler but without Laravel's logging integration and overlap protection.

Requirements
------------

[](#requirements)

- Laravel 11, 12, or 13
- PHP 8.1+

The Pagefind binary is optional — the PHP indexer works without it.

Testing
-------

[](#testing)

**Unit tests** (no Laravel bootstrap required):

```
cd packages/scolta-laravel
./vendor/bin/phpunit
```

**Integration tests** (requires DDEV):

```
cd test-laravel-12
ddev exec php vendor/bin/phpunit --testsuite=Integration
```

**Coding standards:**

```
cd packages/scolta-laravel
composer lint    # Laravel Pint
composer format  # Auto-fix violations
```

Architecture
------------

[](#architecture)

```
scolta-laravel (this package)      scolta-php              scolta-core (browser WASM)
  Artisan commands ──────────> ContentExporter ──────> cleanHtml()
  ScoltaAiService ───────────> AiClient                buildPagefindHtml()
  ScoltaServiceProvider ─────> ScoltaConfig
  Searchable trait ──────────> DefaultPrompts            (runs in browser)
  ScoltaObserver ────────────> PagefindBinary            scoreResults()
  LaravelCacheDriver ────────> CacheDriverInterface      mergeResults()

```

This package handles Laravel-specific concerns: Artisan commands, Eloquent model observation, Blade components, route registration, publishable config/migrations, and middleware. It depends on scolta-php and never on scolta-core directly. Scoring runs client-side via WebAssembly loaded by `scolta.js`.

```
src/
  ScoltaServiceProvider.php              Service provider (auto-discovered)
  Searchable.php                         Trait for Eloquent models
  Commands/BuildCommand.php              artisan scolta:build
  Commands/StatusCommand.php             artisan scolta:status
  Commands/DiscoverCommand.php           artisan scolta:discover
  Commands/DownloadPagefindCommand.php   artisan scolta:download-pagefind
  Http/Controllers/ExpandQueryController.php
  Http/Controllers/SummarizeController.php
  Http/Controllers/FollowUpController.php
  Http/Controllers/HealthController.php
  Models/ScoltaTracker.php               Change tracking model
  Observers/ScoltaObserver.php           Auto-tracking observer
  Services/ScoltaAiService.php           AI service wrapper
  Services/ContentSource.php             Eloquent content source
config/scolta.php                        Publishable configuration
database/migrations/                     Tracker table migration
routes/api.php                           API route definitions
resources/views/components/search.blade.php   component

```

External Services
-----------------

[](#external-services)

Scolta connects to external services under specific conditions. No data is sent automatically — all connections are triggered by developer action or explicit configuration.

### GitHub API (api.github.com)

[](#github-api-apigithubcom)

**When:** A developer runs `php artisan scolta:download-pagefind` to download the Pagefind binary. **What is sent:** A standard HTTPS GET request to `https://api.github.com/repos/CloudCannon/pagefind/releases/latest`. No personally identifiable information is transmitted beyond standard HTTP request headers (IP address, user agent). **Service:** GitHub, operated by GitHub, Inc. (a subsidiary of Microsoft Corporation). **Terms of Service:** **Privacy Statement:**

### Pagefind Binary (GitHub Releases / Pagefind)

[](#pagefind-binary-github-releases--pagefind)

**When:** `php artisan scolta:download-pagefind` downloads the Pagefind binary from GitHub Releases after querying the GitHub API above. **What is sent:** A standard HTTPS GET request to download the release archive. No personally identifiable information is transmitted beyond standard HTTP request headers. **Service:** Pagefind is an open-source project (MIT license) maintained by the Pagefind project. **Pagefind:** **CloudCannon:** **Pagefind License:**

### AI Provider APIs

[](#ai-provider-apis)

**When:** A visitor performs a search and AI features are enabled (`SCOLTA_AI_EXPAND=true` or `SCOLTA_AI_SUMMARIZE=true` in `.env`). AI features are disabled by default and require an API key to be configured. **What is sent:** The user's search query text and selected page content excerpts (for result summarization) are sent to the configured AI provider's API endpoint. See [AI Features and Privacy](#ai-features-and-privacy) for full details on what is and is not transmitted. **Providers:** The specific provider depends on the `SCOLTA_AI_PROVIDER` setting:

- **Anthropic (Claude)** — processes search queries and page excerpts. Terms of Service: Privacy Policy:
- **OpenAI** — processes search queries and page excerpts. Terms of Use: Privacy Policy:
- **OpenAI-compatible endpoints** (including self-hosted Ollama and other providers) — any endpoint configured via `SCOLTA_AI_BASE_URL`. Review the terms and privacy policy of your chosen provider.

No AI API calls are made unless `SCOLTA_API_KEY` is set and AI features are enabled.

About Tag1 Consulting
---------------------

[](#about-tag1-consulting)

Scolta is designed, built, and maintained by [Tag1 Consulting](https://www.tag1.com/). Tag1 has been delivering technology leadership since 2007 and is one of the leading open-source consulting firms in the world.

Tag1 offers AI strategy, architecture, and implementation consulting — from evaluating whether AI search is right for your organization, to production deployment and ongoing tuning. If you need help integrating Scolta, customizing scoring for your content model, or connecting it to your AI provider of choice, [get in touch](https://www.tag1.com/).

Credits
-------

[](#credits)

Scolta is built on [Pagefind](https://pagefind.app/) by [CloudCannon](https://cloudcannon.com/). Without Pagefind, Scolta has no search to score — the index format, WASM search engine, word-position data, and excerpt generation are all Pagefind's. Scolta's contribution is the layer that sits on top: configurable scoring, multi-adapter ranking parity, AI features, and platform glue.

License
-------

[](#license)

MIT

Related Packages
----------------

[](#related-packages)

- [scolta-core](https://github.com/tag1consulting/scolta-core) — Rust/WASM scoring, ranking, and AI layer that runs in the browser.
- [scolta-php](https://github.com/tag1consulting/scolta-php) — PHP library that indexes content into Pagefind-compatible indexes, plus the shared orchestration and AI client.
- [scolta-drupal](https://github.com/tag1consulting/scolta-drupal) — Drupal 10/11 Search API backend with Drush commands, admin settings form, and a search block.
- [scolta-wp](https://github.com/tag1consulting/scolta-wp) — WordPress 6.x plugin with WP-CLI commands, Settings API page, and a `[scolta_search]` shortcode.

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance98

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity54

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

Total

20

Last Release

10d ago

Major Versions

0.3.10 → 1.0.0-rc12026-05-11

### Community

Maintainers

![](https://www.gravatar.com/avatar/31eeec7dde656a2b0ff56548ff6f04cf3d327afde7a75c9f296d08b6a0d8c6f5?d=identicon)[jeremytag1](/maintainers/jeremytag1)

---

Top Contributors

[![jeremyandrews](https://avatars.githubusercontent.com/u/402892?v=4)](https://github.com/jeremyandrews "jeremyandrews (229 commits)")

---

Tags

scoltasearchsearchlaravelaipagefindscolta

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/tag1-scolta-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/tag1-scolta-laravel/health.svg)](https://phpackages.com/packages/tag1-scolta-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k28.4M134](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M120](/packages/laravel-pulse)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)

PHPackages © 2026

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