commonform / trawl
PHP-native build-time full-text search indexer with a dependency-free JS query runtime
Requires
- php: ^8.3
- ext-dom: *
- ext-json: *
- ext-libxml: *
- league/commonmark: ^2.5
- symfony/console: ^7.0
- symfony/css-selector: ^7.0
- symfony/dom-crawler: ^7.0
- symfony/finder: ^7.0
- symfony/yaml: ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-22 09:58:13 UTC
README
PHP-native build-time full-text search indexer with a dependency-free JavaScript query runtime. No Node, no server-side query API, no Elasticsearch — index at build time, query in the browser.
Why Trawl?
Trawl fills a specific gap: PHP shops that want client-side full-text search without adding Node to their toolchain.
| Trawl | Pagefind | Hosted (Algolia, etc.) | |
|---|---|---|---|
| Indexer runtime | PHP (Composer) | Node/npx | SaaS |
| Search runtime | Browser-only JS | Browser-only JS | Server API |
| Node dependency | None | Required for indexing | None |
| Composer install | composer require --dev |
N/A | N/A |
| Chunked index | Yes (CRC32 shards) | Yes | N/A |
| On-premise | Fully self-contained | Fully self-contained | Third-party |
If you already have Node in your build pipeline, Pagefind is excellent and more mature. If you don't, or you want your entire dev dependency tree in Composer, Trawl is your option.
Quickstart
composer require --dev commonform/trawl trawl init trawl build --source=dist/ --output=dist/trawl/
Then in your HTML:
<script type="module"> import { Trawl } from '/trawl.js'; const trawl = await Trawl.init({ baseUrl: '/trawl/' }); const results = await trawl.search('reverse proxy'); // [{ url, title, score, excerpt: '...<mark>reverse proxy</mark>...' }] </script>
Copy runtime/trawl.js from the package into your public directory, or use it directly from vendor/commonform/trawl/runtime/trawl.js.
CLI Commands
trawl build Build the search index
trawl verify Validate manifest + shard + doc consistency
trawl watch Watch for file changes and rebuild incrementally
trawl init Scaffold a trawl.json configuration file
Build
trawl build --source=dist/ --output=dist/trawl/ trawl build --adapter=markdown --source=content/ --output=dist/trawl/ trawl build --incremental # skip unchanged docs trawl build --config=trawl.json # use config file
Verify
trawl verify --index=dist/trawl/
Configuration (trawl.json)
{
"source": "dist/",
"output": "dist/trawl/",
"adapter": "filesystem",
"include": ["**/*.html"],
"exclude": ["**/404.html", "drafts/**"],
"selectors": {
"title": "title",
"heading": "h1, h2, h3",
"body": "main",
"ignore": "[data-trawl-ignore], nav, footer"
},
"field_weights": { "title": 10, "heading": 5, "body": 1 },
"facets": ["tags", "category"],
"language": "en",
"stemmer": "porter2",
"stopwords": "default",
"incremental": true,
"cache_file": ".trawl-cache.json"
}
Adapters
| Adapter | Input | Description |
|---|---|---|
filesystem |
.html files |
Walks a directory of rendered HTML, extracts content via CSS selectors |
markdown |
.md / .mdx files |
Parses YAML frontmatter + Markdown body via league/commonmark |
array |
iterable<Document> |
Programmatic input from CMS integrations |
JS Runtime API
import { Trawl } from './trawl.js'; const trawl = await Trawl.init({ baseUrl: '/trawl/' }); const results = await trawl.search('query', { limit: 10, filters: { tags: 'docs' }, mode: 'and' // 'and' (default) or 'or' }); // results: [{ url, title, score, excerpt, meta }]
Requirements
- Indexer: PHP 8.3+, Composer
- Runtime: Any browser with native
fetch, ES modules, andURL
License
MPL-2.0