typo3-solr
Apache Solr for TYPO3
Compatibility: TYPO3 v13.4 LTS (current) and v14.x (when EXT:solr 14.0 releases). EXT:solr 13.1 targets TYPO3 13.4. A
task/14LTS_compatibilitydev branch is actively being developed. All custom PHP code in this skill uses TYPO3 v14 conventions (PHP 8.2+,#[AsEventListener], constructor promotion).
TYPO3 API First: Always use TYPO3's built-in APIs and EXT:solr's TypoScript/PSR-14 events before creating custom implementations. Do not reinvent what EXT:solr already provides.
Sources
This skill is based on the following authoritative sources:
- EXT:solr Documentation
- EXT:tika Documentation
- Version Matrix
- GitHub: TYPO3-Solr/ext-solr
- GitHub: v14 dev branch
- Apache Solr Reference Guide
- Solr Dense Vector Search
- Solr Text to Vector (LLM)
- typo3-solr.com
- hosted-solr.com
- ddev-typo3-solr
- Mittwald Solr Docs
- helhum/dotenv-connector
1. Architecture Overview
EXT:solr connects TYPO3 CMS to an Apache Solr search server, providing full-text search, faceted navigation, autocomplete, and (since Solr 9.8) vector/semantic search.
graph LR
subgraph typo3 [TYPO3 CMS]
Editor[Editor creates/edits content]
Monitor[Monitoring detects changes]
Queue[Index Queue]
Scheduler[Scheduler Worker]
Plugin[Search Plugin]
end
subgraph solr [Apache Solr Server]
Core[Solr Core]
Schema[Schema / Configset]
end
Editor --> Monitor
Monitor --> Queue
Queue --> Scheduler
Scheduler -->|HTTP POST documents| Core
Plugin -->|HTTP GET query| Core
Core --> Plugin
Document Lifecycle
sequenceDiagram
participant E as Editor
participant T as TYPO3
participant M as Monitor
participant Q as Index Queue
participant S as Scheduler
participant Solr as Solr Server
E->>T: Create/edit record
T->>M: DataHandler triggers PSR-14 event
M->>Q: Add/update queue item
Note over Q: tx_solr_indexqueue_item
S->>Q: Poll for pending items
Q->>S: Return items to index
S->>Solr: POST document (JSON)
Solr-->>S: 200 OK
S->>Q: Mark item as indexed
Component Overview
| Component | Package | Purpose | Required? |
|---|---|---|---|
| EXT:solr | apache-solr-for-typo3/solr |
Core search integration | Yes |
| EXT:tika | apache-solr-for-typo3/tika |
Text/metadata extraction from files | Only for file indexing |
| EXT:solrfal | Funding extension | FAL file indexing into Solr | Only for file indexing |
| EXT:solrconsole | Funding extension | Backend management console | Optional |
| EXT:solrdebugtools | Funding extension | Query debugging, score analysis | Optional (recommended for dev) |
2. Version Compatibility Matrix
| EXT:solr | TYPO3 | Apache Solr | Configset | PHP | EXT:tika | EXT:solrfal |
|---|---|---|---|---|---|---|
| 13.1 | 13.4 | 9.10.1 | ext_solr_13_1_0 |
8.2 - 8.4 | 13.1 | 13.0 |
| 12.1 | 12.4 | 9.10.1 | ext_solr_12_1_0 |
8.1 - 8.3 | 12.1 | 12.0 |
TYPO3 v14 Readiness
EXT:solr does not yet have a stable v14 release. Active development happens on the task/14LTS_compatibility branch (last updated Feb 17, 2026).
Key v14 changes in the dev branch:
- Fluid v5 ViewHelper compatibility
- TSFE removal:
FrontendEnvironment/Tsferefactored toFrontendSimulation/FrontendAwareEnvironment ext_emconf.phpremoved (Composer-only)- TCA
searchFieldsdeprecation handled - FlexForm registration via
registerPlugin()instead ofaddPiFlexFormValue() - New configset:
ext_solr_14_0_0
Testing with the dev branch:
composer require apache-solr-for-typo3/solr:dev-task/14LTS_compatibility
Warning: This branch is WIP. Do not use in production.
CVE-2025-24814 Migration
Apache Solr 9.8.0+ disables loading jar files via lib directive in configsets. The solr-typo3-plugin must be moved from /configsets/ext_solr_*/typo3lib/ to /typo3lib/ at the Solr server root. Docker users: pull image v13.0.1+ -- the migration runs automatically.
3. Installation & Setup
Composer
composer require apache-solr-for-typo3/solr
DDEV Setup
The recommended local development setup uses the ddev-typo3-solr addon:
ddev add-on get ddev/ddev-typo3-solr
ddev restart
Configure .ddev/typo3-solr/config.yaml:
config: 'vendor/apache-solr-for-typo3/solr/Resources/Private/Solr/solr.xml'
typo3lib: 'vendor/apache-solr-for-typo3/solr/Resources/Private/Solr/typo3lib'
configsets:
- name: 'ext_solr_13_1_0'
path: 'vendor/apache-solr-for-typo3/solr/Resources/Private/Solr/configsets/ext_solr_13_1_0'
cores:
- name: 'core_en'
schema: 'english/schema.xml'
- name: 'core_de'
schema: 'german/schema.xml'
Auto-initialize cores on boot in .ddev/config.yaml:
hooks:
post-start:
- exec-host: ddev solrctl apply
Useful DDEV commands:
| Command | Description |
|---|---|
ddev solrctl apply |
Create cores from config |
ddev solrctl wipe |
Delete all cores |
ddev solr version |
Check Solr version |
ddev launch :8984 |
Open Solr Admin UI |
ddev logs -s typo3-solr |
View Solr logs |
Docker (Production)
services:
solr:
image: typo3solr/ext-solr:13.1
ports:
- "8983:8983"
volumes:
- solr-data:/var/solr
restart: unless-stopped
volumes:
solr-data:
driver: local
The image ships default cores for all languages. Persistent data is stored at /var/solr (owned by UID 8983).
Standalone Solr
Deploy the configset from EXT:solr into your Solr installation:
cp -r vendor/apache-solr-for-typo3/solr/Resources/Private/Solr/* $SOLR_INSTALL_DIR/server/solr/
Create cores via core.properties files or REST API:
curl -X POST http://localhost:8983/api/cores -H 'Content-Type: application/json' -d '{
"create": {
"name": "core_en",
"configSet": "ext_solr_13_1_0",
"schema": "english/schema.xml",
"instanceDir": "cores/core_en",
"dataDir": "/var/solr/data/core_en"
}
}'
Managed Hosting: Mittwald
Mittwald provides a managed Solr service via their container platform. Use the Terraform module:
module "solr" {
source = "mittwald/solr/mittwald"
solr_version = "9"
solr_core_name = "typo3"
solr_heap = "2g"
}
Access Solr at http://solr:8983 inside the container. For local debugging:
mw container port-forward --port 8983
Managed Hosting: hosted-solr.com
hosted-solr.com by dkd provides pre-configured Solr cores optimized for EXT:solr. Plans start at EUR 10/month (2 indexes, 4000 documents). After creating a core, configure it in your TYPO3 site config using the provided host, port, and path.
TYPO3 Site Configuration
In config/sites/<identifier>/config.yaml:
solr_enabled_read: true
solr_host_read: solr
solr_port_read: '8983'
solr_scheme_read: http
solr_path_read: /
solr_core_read: core_en
For DDEV, use the DDEV hostname and HTTPS port:
solr_host_read: <project>.ddev.site
solr_port_read: '8984'
solr_scheme_read: https
Environment Configuration (helhum/dotenv-connector)
Never hardcode Solr connection details. Use helhum/dotenv-connector for per-environment configuration:
composer require helhum/dotenv-connector
.env (gitignored):
SOLR_HOST=solr
SOLR_PORT=8983
SOLR_SCHEME=http
SOLR_PATH=/
SOLR_CORE_EN=core_en
SOLR_CORE_DE=core_de
.env.example (committed to VCS):
SOLR_HOST=solr
SOLR_PORT=8983
SOLR_SCHEME=http
SOLR_PATH=/
SOLR_CORE_EN=core_en
SOLR_CORE_DE=core_de
In config/system/additional.php, override site config values programmatically or use a post-processing approach. For simple setups, keep .env values and reference them in deployment scripts that generate site config YAML per environment.
4. EXT:tika -- When You Need It (and When Not)
EXT:tika integrates Apache Tika for metadata extraction, language detection, and text extraction from ~1200 file formats.
graph TD
Question{"Do you index files?<br/>(PDF, DOCX, XLSX)"}
Question -->|Yes| NeedTika[Install EXT:tika + EXT:solrfal]
Question -->|No| SkipTika[Skip -- EXT:solr handles<br/>pages and records natively]
NeedTika --> TikaServer["Run Tika Server 3.2.2+<br/>(Docker recommended)"]
Three backends (choose one):
| Backend | Recommended? | Setup |
|---|---|---|
| Tika Server | Yes | Standalone Docker container, newest Tika version |
| Solr Cell | Acceptable | Uses Tika built into Solr, no extra service needed |
| Tika App | Deprecated | Requires Java on webserver, do not use |
When you NEED EXT:tika:
- File indexing via EXT:solrfal (search inside PDFs, Word documents, etc.)
- Automatic FAL metadata enrichment (EXIF, XMP, document properties)
- Language detection on uploaded files
When you DON'T need it:
- Only indexing pages and structured records (news, events, products) via Index Queue
- EXT:solr handles page content and record fields natively without Tika
Version: EXT:tika 13.1. Requires Tika Server 3.2.2+ (CVE-2025-54988 fix).
See SKILL-SOLRFAL.md for complete file indexing setup.
5. Configset & Schema
The configset defines how Solr processes and indexes text. EXT:solr ships configsets at Resources/Private/Solr/configsets/ext_solr_13_1_0/.
Structure
ext_solr_13_1_0/
├── conf/
│ ├── solrconfig.xml
│ ├── english/
│ │ └── schema.xml
│ ├── german/
│ │ └── schema.xml
│ └── ... (other languages)
└── typo3lib/ # moved to server root since Solr 9.8
Each language has its own schema.xml with language-specific analyzers (stemmer, stop words).
Dynamic Field Types
Use dynamic field suffixes to add custom fields without modifying the schema:
| Suffix | Type | Multi | Example |
|---|---|---|---|
_stringS |
string (not analyzed) | No | category_stringS |
_stringM |
string (not analyzed) | Yes | tags_stringM |
_textS |
text (analyzed) | No | description_textS |
_textM |
text (analyzed) | Yes | keywords_textM |
_intS |
integer | No | year_intS |
_floatS |
float | No | price_floatS |
_dateS |
date | No | published_dateS |
_boolS |
boolean | No | active_boolS |
Full reference: Dynamic Fields Appendix
Site Hash Strategy
Since EXT:solr 13.0, the default site hash strategy changed from domain-based (deprecated) to site-identifier-based. Configure in Extension Settings:
siteHashStrategy = 1(site-identifier, default and recommended)siteHashStrategy = 0(domain, deprecated, removed in 13.1.x+)
If upgrading from 12.x, you must re-index after switching strategies.
6. Index Queue Configuration
The Index Queue is the central mechanism for getting TYPO3 records into Solr.
graph LR
subgraph monitoring [Record Monitoring]
Edit[Record created/edited]
Event[PSR-14 DataUpdate Event]
end
subgraph queue [Index Queue]
Item["tx_solr_indexqueue_item"]
end
subgraph processing [Processing]
Worker[Scheduler: Index Queue Worker]
Solr[Solr Server]
end
Edit --> Event
Event --> Item
Worker -->|polls| Item
Worker -->|POST document| Solr
Pages
Pages are indexed out of the box. No additional configuration needed. The page indexer sends the rendered page content to Solr.
Custom Records
Index any TYPO3 record table via TypoScript. Full example for EXT:news:
plugin.tx_solr.index.queue {
news = 1
news {
type = tx_news_domain_model_news
fields {
abstract = teaser
author = author
authorEmail_stringS = author_email
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10 {
field = bodytext
noTrimWrap = || |
}
}
}
category_stringM = SOLR_RELATION
category_stringM {
localField = categories
multiValue = 1
}
keywords = SOLR_MULTIVALUE
keywords {
field = keywords
}
tags_stringM = SOLR_RELATION
tags_stringM {
localField = tags
multiValue = 1
}
url = TEXT
url {
typolink.parameter = {$plugin.tx_news.settings.detailPid}
typolink.additionalParams = &tx_news_pi1[controller]=News&tx_news_pi1[action]=detail&tx_news_pi1[news]={field:uid}
typolink.additionalParams.insertData = 1
typolink.returnLast = url
}
}
attachments {
fields = related_files
}
}
}
Content Objects
| Object | Purpose |
|---|---|
SOLR_CONTENT |
Strips HTML/RTE from field content |
SOLR_RELATION |
Resolves relations (categories, tags, etc.), supports multiValue = 1 |
SOLR_MULTIVALUE |
Splits a comma-separated field into multiple values |
Records Outside Siteroot
To index records stored in sysfolders outside the site tree:
plugin.tx_solr.index.queue.news {
additionalPageIds = 45,48
}
Enable monitoring in Extension Settings: "Enable tracking of records outside siteroot".
Monitoring
EXT:solr detects record changes via PSR-14 events. Two modes:
- Immediate (default): changes are processed directly during the DataHandler operation
- Delayed: changes are queued in
tx_solr_eventqueue_itemand processed by a scheduler task ("Event Queue Worker")
Configure via Extension Settings: monitoringType.
7. Search Configuration
Essential Settings
plugin.tx_solr {
enabled = 1
search {
targetPage = 42
initializeWithEmptyQuery = 1
showResultsOfInitialEmptyQuery = 0
trustedFields = url
keepExistingParametersForNewSearches = 1
}
}
Faceting
plugin.tx_solr.search {
faceting = 1
faceting {
facets {
contentType {
label = Content Type
field = type
}
category {
label = Category
field = category_stringM
}
year {
label = Year
field = year_intS
type = queryGroup
queryGroup {
2026 {
query = [2026-01-01T00:00:00Z TO 2026-12-31T23:59:59Z]
}
2025 {
query = [2025-01-01T00:00:00Z TO 2025-12-31T23:59:59Z]
}
2024 {
query = [2024-01-01T00:00:00Z TO 2024-12-31T23:59:59Z]
}
}
}
}
}
}
Facet types: options (default), queryGroup, hierarchy, dateRange, numericRange.
Suggest / Autocomplete
plugin.tx_solr {
suggest = 1
suggest {
numberOfSuggestions = 10
suggestField = spell
showTopResults = 1
numberOfTopResults = 5
additionalTopResultsFields = url,type
}
}
The built-in suggest uses the devbridge/jQuery-Autocomplete library. For a jQuery-free approach, see SKILL-FRONTEND.md.
Sorting, Highlighting, Spellcheck
plugin.tx_solr.search {
sorting = 1
sorting {
defaultOrder = asc
options {
relevance {
field = relevance
label = Relevance
}
title {
field = sortTitle
label = Title
}
created {
field = created
label = Date
}
}
}
results {
resultsHighlighting = 1
resultsHighlighting {
fragmentSize = 200
wrap = <mark>|</mark>
}
}
spellchecking = 1
}
Route Enhancers
SEO-friendly search URLs:
routeEnhancers:
SolrSearch:
type: SolrFacetMaskAndCombineEnhancer
extensionKey: solr
solr:
type: Extbase
extension: Solr
plugin: pi_results
routes:
- routePath: '/search/{q}'
_controller: 'Search::results'
_arguments:
q: q
8. Fluid Templates & Frontend
Template Paths
plugin.tx_solr {
view {
templateRootPaths.10 = EXT:my_ext/Resources/Private/Templates/Solr/
partialRootPaths.10 = EXT:my_ext/Resources/Private/Partials/Solr/
layoutRootPaths.10 = EXT:my_ext/Resources/Private/Layouts/Solr/
}
}
Key Templates
| Template | Purpose |
|---|---|
Templates/Search/Results.html |
Main search results page |
Templates/Search/Form.html |
Search form |
Partials/Result/Document.html |
Single result item |
Partials/Facets/OptionsFacet.html |
Options facet rendering |
Partials/Facets/QueryGroupFacet.html |
Query group facet |
Copy default templates from EXT:solr to your extension path and modify them.
9. PSR-14 Events Reference
Monitoring Events
| Event | Fired when |
|---|---|
VersionSwappedEvent |
A version is swapped (workspace publish) |
RecordMovedEvent |
A record is moved |
RecordGarbageCheckEvent |
A garbage check is triggered |
RecordDeletedEvent |
A record is deleted |
PageMovedEvent |
A page is moved |
ContentElementDeletedEvent |
A content element is deleted |
Data Update Processing Events
| Event | Fired when |
|---|---|
ProcessingFinishedEvent |
Data update processing is complete |
DelayedProcessingQueuingFinishedEvent |
Update queued in event queue (delayed mode) |
DelayedProcessingFinishedEvent |
Delayed processing complete (scheduler) |
Indexing Events
| Event | Fired when |
|---|---|
BeforePageDocumentIsProcessedForIndexingEvent |
Before page document is processed (add extra documents) |
AfterPageDocumentIsCreatedForIndexingEvent |
After page document created (replace/substitute) |
BeforeDocumentIsProcessedForIndexingEvent |
Before non-page record document is processed |
BeforeDocumentsAreIndexedEvent |
Before documents are sent to Solr (add custom fields) |
Other Events
| Event | Fired when |
|---|---|
AfterFacetIsParsedEvent |
Facet component modification |
AfterUriIsProcessedEvent |
URI building in search context |
AfterSiteHashHasBeenDeterminedForSiteEvent |
Override calculated site hash |
Example: Add Custom Fields to Documents (v14 Style)
<?php
declare(strict_types=1);
namespace MyVendor\MyExt\EventListener;
use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentsAreIndexedEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'my-ext/enrich-solr-documents')]
final class EnrichSolrDocuments
{
public function __invoke(BeforeDocumentsAreIndexedEvent $event): void
{
foreach ($event->getDocuments() as $document) {
$document->addField('custom_score_floatS', $this->calculateScore($document));
}
}
private function calculateScore(\ApacheSolrForTypo3\Solr\System\Solr\Document\Document $document): float
{
return match ($document->getField('type')['value'] ?? '') {
'pages' => 1.0,
'tx_news_domain_model_news' => 0.8,
default => 0.5,
};
}
}
v13 fallback (if #[AsEventListener] is not available): register in Services.yaml:
MyVendor\MyExt\EventListener\EnrichSolrDocuments:
tags:
- name: event.listener
identifier: 'my-ext/enrich-solr-documents'
10. Independent Indexer (Custom Data)
For external data or when the RecordIndexer is not sufficient:
<?php
declare(strict_types=1);
namespace MyVendor\MyExt\Indexer;
use ApacheSolrForTypo3\Solr\ConnectionManager;
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
final class ExternalDataIndexer
{
public function __construct(
private readonly ConnectionManager $connectionManager,
private readonly SiteRepository $siteRepository,
) {}
public function index(array $rows, int $rootPageId = 1, int $language = 0): void
{
$site = $this->siteRepository->getSiteByRootPageId($rootPageId);
$connection = $this->connectionManager->getConnectionByPageId($rootPageId, $language);
$documents = [];
foreach ($rows as $row) {
$document = new Document();
$document->setField('id', 'external_' . $row['uid']);
$document->setField('variantId', 'external_' . $row['uid']);
$document->setField('type', 'external_record');
$document->setField('appKey', 'EXT:solr');
$document->setField('access', ['r:0']);
$document->setField('site', $site->getDomain());
$document->setField('siteHash', $site->getSiteHash());
$document->setField('uid', $row['uid']);
$document->setField('pid', $rootPageId);
$document->setField('title', $row['title']);
$document->setField('content', $row['content']);
$document->setField('url', $row['url']);
$documents[] = $document;
}
$connection->getWriteService()->addDocuments($documents);
}
public function clearIndex(string $type = 'external_record'): void
{
$connections = $this->connectionManager->getAllConnections();
foreach ($connections as $connection) {
$connection->getWriteService()->deleteByType($type);
}
}
}
CLI Command Wrapper
<?php
declare(strict_types=1);
namespace MyVendor\MyExt\Command;
use MyVendor\MyExt\Indexer\ExternalDataIndexer;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'myext:index-external', description: 'Index external data into Solr')]
final class IndexExternalCommand extends Command
{
public function __construct(
private readonly ExternalDataIndexer $indexer,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$rows = $this->fetchExternalData();
$io->info(sprintf('Indexing %d records...', count($rows)));
$this->indexer->index($rows);
$io->success('Done.');
return Command::SUCCESS;
}
private function fetchExternalData(): array
{
// Implement your data fetching logic
return [];
}
}
11. LLM & Vector Search (Solr Server Native)
Apache Solr 9.8+ includes a native LLM module for semantic/vector search. This runs on the Solr server side -- EXT:solr queries the results.
graph LR
subgraph indexing [Index Time]
Text[Document text]
URP["TextToVectorUpdateProcessor"]
API1["Embedding API<br/>(OpenAI, Mistral, etc.)"]
Vector1[Dense Vector]
SolrIdx["Solr Index<br/>(DenseVectorField)"]
end
subgraph querying [Query Time]
Query[User query text]
QP["knn_text_to_vector parser"]
API2["Embedding API"]
Vector2[Query Vector]
KNN["KNN Search"]
Results[Ranked Results]
end
Text --> URP --> API1 --> Vector1 --> SolrIdx
Query --> QP --> API2 --> Vector2 --> KNN --> Results
SolrIdx -.->|similarity| KNN
Prerequisites
- Apache Solr 9.8+ with the
llmmodule enabled - An external embedding API account (OpenAI, Mistral AI, Cohere, or HuggingFace)
Schema: DenseVectorField
Add to your schema (or use a custom configset):
<fieldType name="knn_vector" class="solr.DenseVectorField"
vectorDimension="1536"
similarityFunction="cosine"
knnAlgorithm="hnsw"/>
<field name="vector" type="knn_vector" indexed="true" stored="true"/>
vectorDimension must match your embedding model (e.g., OpenAI text-embedding-3-small = 1536 dimensions).
Model Configuration
Register the query parser in solrconfig.xml:
<queryParser name="knn_text_to_vector"
class="org.apache.solr.llm.textvectorisation.search.TextToVectorQParserPlugin"/>
Upload a model definition:
curl -XPUT 'http://localhost:8983/solr/core_en/schema/text-to-vector-model-store' \
--data-binary @model.json -H 'Content-type:application/json'
model.json (OpenAI example):
{
"class": "dev.langchain4j.model.openai.OpenAiEmbeddingModel",
"name": "openai-embed",
"params": {
"baseUrl": "https://api.openai.com/v1",
"apiKey": "sk-...",
"modelName": "text-embedding-3-small",
"timeout": 60,
"maxRetries": 3
}
}
Supported providers: OpenAI, Mistral AI, Cohere, HuggingFace. See LangChain4j Embedding Models for all parameters.
Indexing with Vectors
Add an update processor chain in solrconfig.xml:
<updateRequestProcessorChain name="vectorisation">
<processor class="solr.llm.textvectorisation.update.processor.TextToVectorUpdateProcessorFactory">
<str name="inputField">_text_</str>
<str name="outputField">vector</str>
<str name="model">openai-embed</str>
</processor>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
Two-pass strategy (recommended for production): index documents normally first, then enrich with vectors in a second pass to avoid blocking the indexing pipeline with slow API calls.
Querying
Semantic search (text in, vector out):
?q={!knn_text_to_vector model=openai-embed f=vector topK=10}customer complaints handling
Raw vector search:
?q={!knn f=vector topK=10}[0.1, 0.2, 0.3, ...]
Threshold search (all documents above similarity):
?q={!vectorSimilarity f=vector minReturn=0.7}[0.1, 0.2, 0.3, ...]
Hybrid search (BM25 + vector re-ranking):
?q=customer complaints
&rq={!rerank reRankQuery=$rqq reRankDocs=50 reRankWeight=2}
&rqq={!knn_text_to_vector model=openai-embed f=vector topK=50}customer complaints
Pre-Filtering
Combine vector search with traditional filters:
?q={!knn_text_to_vector model=openai-embed f=vector topK=10 preFilter=type:pages}search query
EXT:solr Integration
EXT:solr 13.1 has initial vector search support. For custom integration, use a PSR-14 event listener on BeforeDocumentsAreIndexedEvent to add vector data to documents via your own embedding service call.
Note: Full native vector search support in EXT:solr (Index Queue -> vector fields, Fluid templates for vector results) is still evolving. Monitor the GitHub repository for updates.
Use Cases
| Use Case | Approach |
|---|---|
| Smart site search | knn_text_to_vector finds semantically similar content |
| Similar content | Given a document's vector, find K nearest neighbors |
| Multilingual bridge | Embeddings can match across languages |
| FAQ matching | Match user questions to FAQ answers despite different wording |
| RAG preparation | Use Solr as retrieval layer for LLM-powered Q&A |
| File discovery | Combined with solrfal + Tika: semantic search over documents |
12. Debugging & Troubleshooting
This section is the core reference for diagnosing and fixing EXT:solr issues. If you are new to EXT:solr, start with the step-by-step guide below before jumping to the reference sections.
Step-by-Step Troubleshooting Guide (Beginner)
If search is not working and you have no idea where to start, follow these steps in order. Each step explains what you are checking, where to find it, and what the result means.
Step 1: Understand the Architecture
Before debugging, know how EXT:solr works. There are four parts that must all work:
┌─────────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. Connection │───▶│ 2. Indexing │───▶│ 3. Solr Core │───▶│ 4. Frontend │
│ TYPO3 ↔ Solr │ │ Queue+Worker │ │ Documents │ │ Search+Fluid │
└─────────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
- Connection: TYPO3 must know where Solr is running (host, port, core name)
- Indexing: TYPO3 must send your content (pages, news, etc.) to Solr via the Index Queue and a Scheduler task
- Solr Core: The Solr server stores documents and makes them searchable
- Frontend: The search plugin sends queries to Solr and renders results via Fluid templates
A problem at any of these four parts breaks search. The steps below check each part from left to right.
Step 2: Is Solr Reachable? (Connection Check)
Where: TYPO3 Backend → Admin Tools → Reports → Status Report
What you see: A list of status checks. Look for entries about "Solr":
- All green ✅ = Solr is reachable, connection works → go to Step 3
- Red/yellow ⚠️ = Solr is not reachable → fix connection first
If red -- things to check:
Open your site configuration file config/sites/<your-site>/config.yaml and verify these settings:
solr_enabled_read: true
solr_host_read: solr # hostname where Solr runs
solr_port_read: '8983' # port (8983 for standalone, 8984 for DDEV)
solr_scheme_read: http # http or https (DDEV uses https)
solr_path_read: / # usually just /
solr_core_read: core_en # must match the actual core name in Solr
For DDEV users:
solr_host_read: <your-project>.ddev.site
solr_port_read: '8984'
solr_scheme_read: https
Quick test from the terminal:
# DDEV: test if Solr responds
ddev exec curl -s http://solr:8983/solr/core_en/admin/ping
# Expected: {"status":"OK"}
# If "Connection refused": Solr container is not running → ddev restart
After fixing: Always click "Initialize connection" in the EXT:solr backend module (Web → Search → Overview tab).
Step 3: Is Content in the Index Queue? (Indexing Check)
Where: TYPO3 Backend → Web → Search → "Index Queue" tab
What you see: A table showing record types (pages, news, etc.) with counts.
| What you see | What it means | What to do |
|---|---|---|
| Table is empty | No content has been queued for Solr | You need to initialize the queue (see below) |
| Items with count > 0, errors = 0 | Content is queued and indexed | Go to Step 4 |
| Items with errors > 0 | Indexing failed for some records | Read the error messages (see below) |
If the queue is empty:
-
Check that EXT:solr TypoScript templates are included on the root page:
- Backend → Web → Template module → root page → "Includes" tab
- You must see "Search - Base Configuration" in the list
- If it's missing, add it: click "Include static (from extensions)" and select "Search - Base Configuration (solr)"
-
Initialize the queue:
- Go to Web → Search → "Index Queue" tab
- Select the checkboxes for the record types you want to index (e.g., "pages")
- Click "Queue selected content"
- The table should now show a count
If items have errors:
The errors column tells you what went wrong. Common errors:
| Error message | What it means | Fix |
|---|---|---|
| "Could not resolve host" | Solr server unreachable | Go back to Step 2 |
| "Document is missing mandatory uniqueKey field: id" | The document has no ID | Check your TypoScript field mapping |
| "OutOfMemoryError" | PHP or Solr ran out of memory | Increase memory_limit for the scheduler |
| "HTTP 404" | The Solr core doesn't exist | Check core name, run ddev solrctl apply |
Step 4: Have Documents Arrived in Solr? (Solr Check)
Even if the queue shows no errors, documents might not have been sent to Solr yet. The queue only fills the to-do list -- a Scheduler task actually sends them.
Check if the Scheduler task exists:
- Backend → System → Scheduler
- Look for a task called "Index Queue Worker" (class:
ApacheSolrForTypo3\Solr\Task\IndexQueueWorkerTask) - If it doesn't exist: create it (Add task → select "Index Queue Worker" → save)
- If it exists: check "Last execution" -- has it run recently?
Run it manually now:
- Click the "play" button next to the Index Queue Worker task
Verify documents are in Solr:
Open the Solr Admin UI:
- DDEV: run
ddev launch :8984(opens in browser) - Production: navigate to
http://your-solr-host:8983/solr/
In Solr Admin UI:
- Select your core from the dropdown (e.g.,
core_en) - Click "Query" in the left menu
- Leave
qas*:*(means "all documents") - Click "Execute Query"
- Check
numFoundin the response:
| numFound | What it means | What to do |
|---|---|---|
| 0 | No documents at all | Scheduler task didn't run, or indexing failed silently → enable logging (see 12.0 Layer 2 below) |
| > 0 | Documents exist in Solr | Go to Step 5 |
Useful queries to run in Solr Admin:
# How many documents per type?
q=*:*&rows=0&facet=true&facet.field=type
# Show 5 page documents with their key fields
q=*:*&fq=type:pages&rows=5&fl=uid,title,url,siteHash
# Search for a specific term
q=your search term&fl=uid,title,score
Step 5: Does the Frontend Show Results? (Frontend Check)
Where: Open the page with the search plugin in the frontend, type a search term you know exists in Solr.
| What you see | What it means | What to do |
|---|---|---|
| "Search is currently not available" | Plugin can't connect to Solr | Go back to Step 2 -- connection issue from the frontend |
| Search works, but 0 results | Query runs but finds nothing | Check site hash, access field (see below) |
| Results appear but wrong | Documents found but wrong content/order | Check field mapping, boosting (see below) |
| Results appear correctly ✅ | Everything works! | Done |
If 0 results even though Solr has documents:
The most common cause is a site hash mismatch. EXT:solr adds a siteHash to every document to prevent cross-site contamination. If the hash during indexing differs from the hash during searching, no results appear.
Check in Solr Admin:
# What siteHash values exist in the index?
q=*:*&rows=0&facet=true&facet.field=siteHash
Compare with what EXT:solr expects: open Extension Settings (Admin Tools → Settings → Extension Configuration → solr) and check siteHashStrategy. Since EXT:solr 13.0, the default is site-identifier (not domain).
If the hashes don't match: re-index (Backend → Web → Search → Index Queue → clear and re-queue all).
Other causes of 0 results:
plugin.tx_solr.search.targetPagepoints to wrong page → set it to the page UID containing the search pluginplugin.tx_solr.enabledis0somewhere in TypoScript → check for override- Access restrictions: the
accessfield on documents doesn't match the frontend user → test in Solr Admin withq=*:*&fl=uid,title,access
Step 6: Something Else is Wrong -- Enable Logging
If the steps above didn't solve it, enable logging to see exactly what EXT:solr does:
1. Add TypoScript logging (on the root page, dev/staging only):
plugin.tx_solr.logging {
debugOutput = 1
exceptions = 1
indexing = 1
query.rawGet = 1
query.queryString = 1
query.searchWords = 1
}
2. Write logs to a file (in config/system/additional.php):
$GLOBALS['TYPO3_CONF_VARS']['LOG']['ApacheSolrForTypo3']['Solr']['writerConfiguration'] = [
\Psr\Log\LogLevel::DEBUG => [
\TYPO3\CMS\Core\Log\Writer\FileWriter::class => [
'logFile' => \TYPO3\CMS\Core\Core\Environment::getVarPath() . '/log/solr.log',
],
],
];
3. Watch the log file:
# DDEV
ddev exec tail -f var/log/solr.log
# Then trigger the action: run the scheduler, or do a search in the frontend
What to look for in the log:
[ERROR]lines → these tell you exactly what failedSolr raw GET: http://...→ the exact URL sent to Solr (paste it in a browser to test)Response: 0 results→ Solr answered but found nothing (site hash? access? wrong field?)
4. Also check: Admin Tools → Log module (filter by component "ApacheSolrForTypo3")
Step 7: Still Stuck? Escalation Path
If all six steps above didn't solve it:
- Ask in Slack: TYPO3 Slack #ext-solr -- the maintainers (dkd) are active here
- Search GitHub Issues: TYPO3-Solr/ext-solr/issues -- your problem might already be reported
- dkd support: Professional support -- paid support from the extension maintainers
- Collect info for a bug report: Solr version, EXT:solr version, PHP version, TYPO3 version, error messages from the log, Solr Admin query results
12.0 How to Get Debug Data
Before you can fix anything, you need to see what EXT:solr is doing. There are six layers of debug data, from quick checks to deep inspection:
graph TD
subgraph quick ["1. Quick Checks (no config needed)"]
Reports["TYPO3 Reports Module"]
Backend["EXT:solr Backend Module"]
SolrUI["Solr Admin UI"]
end
subgraph config ["2. Enable Debug Output"]
TSLog["TypoScript logging.*"]
FileLog["FileWriter → var/log/solr.log"]
DebugTools["EXT:solrdebugtools"]
end
subgraph deep ["3. Deep Inspection"]
SolrDebug["Solr debugQuery=true"]
DB["SQL on tx_solr_* tables"]
CLI["CLI curl to Solr API"]
end
Reports --> TSLog
Backend --> TSLog
SolrUI --> SolrDebug
TSLog --> FileLog
FileLog --> DB
DebugTools --> SolrDebug
Layer 1: Quick checks (zero configuration)
TYPO3 Reports module (Admin Tools -> Reports -> Status Report):
- Shows connection status, configset match, core availability per language
- If anything is red/yellow here, fix it before debugging further
EXT:solr backend module (Web -> Search):
- Overview tab: connection status per site and language
- Index Queue tab: record counts by type, error counts, last indexed timestamp
- Index Fields tab: all fields in the Solr schema with types and stored/indexed flags
- Stop Words / Synonyms tabs: current configuration on the Solr core
Solr Admin UI (DDEV: ddev launch :8984, Production: http://host:8983/solr/):
- Dashboard: JVM memory, Solr version, uptime
- Core Selector (dropdown): pick the core you want to inspect
- Query tab: run queries directly against the core
- Schema Browser: inspect field types, stored/indexed flags, sample values
- Analysis tab: paste text to see how Solr tokenizes and stems it
Layer 2: Enable TypoScript debug logging
Add to your root page TypoScript setup (dev/staging only):
plugin.tx_solr.logging {
debugOutput = 1
exceptions = 1
indexing = 1
indexing.indexQueueInitialization = 1
indexing.pageIndexed = 1
indexing.queue.news = 1
query.rawGet = 1
query.rawPost = 1
query.queryString = 1
query.searchWords = 1
}
| Setting | What it logs |
|---|---|
debugOutput |
Writes debug info to the TYPO3 devlog |
exceptions |
Full exception stack traces from Solr communication |
indexing |
Every document sent to Solr during indexing |
indexing.indexQueueInitialization |
Which tables were initialized into the queue |
indexing.pageIndexed |
Each page after it was indexed (URL, fields, response) |
indexing.queue.<name> |
Per-config logging (e.g., queue.news, queue.events) |
query.rawGet |
The exact HTTP GET URL sent to Solr |
query.rawPost |
POST body sent to Solr (for complex queries) |
query.queryString |
The parsed Solr query string |
query.searchWords |
The user's search input after processing |
Where does the output go? By default, to the TYPO3 logging system. To get a file you can tail -f:
// config/system/additional.php
$GLOBALS['TYPO3_CONF_VARS']['LOG']['ApacheSolrForTypo3']['Solr']['writerConfiguration'] = [
\Psr\Log\LogLevel::DEBUG => [
\TYPO3\CMS\Core\Log\Writer\FileWriter::class => [
'logFile' => \TYPO3\CMS\Core\Core\Environment::getVarPath() . '/log/solr.log',
],
],
];
Then watch live:
# DDEV
ddev exec tail -f var/log/solr.log
# Standalone
tail -f /var/www/html/var/log/solr.log
Also check the TYPO3 system log: Admin Tools -> Log module, filter by component ApacheSolrForTypo3.
Layer 3: Solr debugQuery (score explanation)
In Solr Admin UI -> Query tab, add debugQuery=true to see why a document ranks where it does:
q=typo3 solr&debugQuery=true&fl=uid,title,score
The response includes a debug section:
{
"debug": {
"rawquerystring": "typo3 solr",
"querystring": "typo3 solr",
"parsedquery": "(title:typo3 | content:typo3) (title:solr | content:solr)",
"QParser": "ExtendedDismaxQParser",
"explain": {
"4f547484...": "\n7.256 = sum of:\n 3.628 = weight(title:typo3) ...\n 3.628 = weight(title:solr) ..."
},
"timing": {
"time": 2.0,
"prepare": { "time": 1.0 },
"process": { "time": 1.0 }
}
}
}
Key fields in the debug response:
| Field | What it tells you |
|---|---|
parsedquery |
How Solr interpreted your search terms (which fields, operators) |
explain |
Per-document score breakdown: which fields matched, TF-IDF/BM25 weights |
timing |
Query execution time per phase (prepare, process) |
rawquerystring |
The original query before parsing |
Reading the explain output: Each line shows a score component. Higher = more relevant. Look for:
- Which fields contribute most (title vs content)
- Boost values being applied
- Whether coord (coordination) penalizes partial matches
Layer 4: EXT:solrdebugtools (funding extension)
EXT:solrdebugtools adds visual debugging to the frontend:
- Debug\Query ViewHelper: shows the Solr query, parsed query, and timing in the frontend
- Explain display: per-result score breakdown as pie chart
- Query parameters: see all parameters sent to Solr
Add to your Fluid template during development:
<solrdebug:query resultSet="{resultSet}" />
This renders a debug panel directly in the search results page showing the full Solr request and response.
Layer 5: CLI curl to Solr API
Bypass TYPO3 entirely and talk to Solr directly:
# Ping (is Solr alive?)
ddev exec curl -s http://solr:8983/solr/core_en/admin/ping | python3 -m json.tool
# Total document count
ddev exec curl -s 'http://solr:8983/solr/core_en/select?q=*:*&rows=0' | python3 -m json.tool
# Search with debug
ddev exec curl -s 'http://solr:8983/solr/core_en/select?q=test&debugQuery=true&fl=uid,title,score&wt=json' | python3 -m json.tool
# Check a specific document by uid
ddev exec curl -s 'http://solr:8983/solr/core_en/select?q=uid:42&fl=*' | python3 -m json.tool
# List all field names in the index
ddev exec curl -s 'http://solr:8983/solr/core_en/admin/luke?numTerms=0&wt=json' | python3 -m json.tool | grep '"name"'
# Document count by type
ddev exec curl -s 'http://solr:8983/solr/core_en/select?q=*:*&rows=0&facet=true&facet.field=type' | python3 -m json.tool
# Check what siteHash values exist
ddev exec curl -s 'http://solr:8983/solr/core_en/select?q=*:*&rows=0&facet=true&facet.field=siteHash' | python3 -m json.tool
# Analyze how a term is tokenized (for field type "text")
ddev exec curl -s 'http://solr:8983/solr/core_en/analysis/field?analysis.fieldname=content&analysis.fieldvalue=TYPO3+Content+Management&wt=json' | python3 -m json.tool
From host (replace ddev exec curl -s http://solr:8983 with curl -sk https://<project>.ddev.site:8984).
Layer 6: Database-level inspection
Query the TYPO3 database to see what EXT:solr thinks the state is:
-- How many items per type/config, and how many have errors?
SELECT item_type, indexing_configuration,
COUNT(*) as total,
SUM(CASE WHEN indexed > 0 THEN 1 ELSE 0 END) as indexed,
SUM(CASE WHEN errors != '' THEN 1 ELSE 0 END) as errors,
SUM(CASE WHEN changed > indexed THEN 1 ELSE 0 END) as stale
FROM tx_solr_indexqueue_item
GROUP BY item_type, indexing_configuration;
-- Show the actual error messages
SELECT uid, item_type, item_uid, LEFT(errors, 200) as error_preview
FROM tx_solr_indexqueue_item
WHERE errors != ''
ORDER BY uid DESC LIMIT 20;
-- When was the last successful indexing run?
SELECT MAX(FROM_UNIXTIME(indexed)) as last_indexed
FROM tx_solr_indexqueue_item
WHERE indexed > 0;
-- What is the site hash stored for a specific page?
SELECT uid, title, slug
FROM pages
WHERE uid = 1;
-- Then check: the site hash comes from the site identifier in config.yaml
Putting it all together: Debug workflow
graph TD
Problem["Something's wrong"]
Problem --> Reports["1. Check Reports module"]
Reports -->|Red| Connection["Fix connection (12.2)"]
Reports -->|Green| Queue["2. Check Index Queue in backend module"]
Queue -->|Empty| Init["Initialize queue, check TypoScript"]
Queue -->|Errors| SQL["3. Read error messages from DB"]
Queue -->|OK| SolrAdmin["4. Query Solr Admin directly"]
SolrAdmin -->|No docs| Scheduler["Run scheduler, check indexing log"]
SolrAdmin -->|Docs exist| Frontend["5. Check frontend TypoScript"]
Frontend -->|No results| TS["Enable query logging, check siteHash"]
Frontend -->|Wrong results| Debug["6. debugQuery=true, check scoring"]
Scheduler --> Log["Enable indexing logging, tail solr.log"]
12.1 Where to Start: Diagnostic Flowchart
graph TD
Start["Problem with Solr?"]
Start --> A{"Reports module:<br/>all green?"}
A -->|No| Fix1["Fix connection config<br/>(see 12.2)"]
A -->|Yes| B{"Index Queue:<br/>items present?"}
B -->|No| Fix2["Initialize queue<br/>(see 12.3)"]
B -->|Yes| C{"Items have<br/>errors?"}
C -->|Yes| Fix3["Check error messages<br/>(see 12.3)"]
C -->|No| D{"Solr Admin:<br/>documents exist?"}
D -->|No| Fix4["Run scheduler worker<br/>(see 12.3)"]
D -->|Yes| E{"Frontend shows<br/>results?"}
E -->|No| Fix5["Check TypoScript,<br/>site hash (see 12.4)"]
E -->|Yes| Done["Working!"]
Step 1: Check TYPO3 Reports module (Admin Tools -> Reports -> Status Report). All Solr-related checks must be green.
Step 2: Open Solr Admin UI. DDEV: ddev launch :8984. Production: https://your-host:8983/solr/.
Step 3: EXT:solr backend module -> "Index Queue" tab. Are items queued? Any errors?
12.2 Connection Problems
"Search is currently not available" -- full checklist:
- Verify site config values:
solr_host_read,solr_port_read,solr_scheme_read,solr_path_read - Click "Initialize connection" in the EXT:solr backend module after ANY config change
- Test from CLI:
# Inside DDEV container ddev exec curl -s http://solr:8983/solr/core_en/admin/ping # From host curl -sk https://<project>.ddev.site:8984/solr/core_en/admin/ping - DDEV networking: Inside the container, Solr is reachable at hostname
solron port8983. From the host, use<project>.ddev.site:8984with HTTPS. - Firewall: Ensure port 8983 (or 8984 for DDEV) is open
- HTTPS: DDEV uses self-signed certificates. Use
solr_scheme_read: httpswith DDEV.
12.3 Indexing Problems
Nothing in Index Queue
- TypoScript not loaded: Check the Template module on the root page. EXT:solr static templates must be included.
- Queue not initialized: Backend module -> "Index Queue" tab -> select record types -> click "Queue selected content"
- Missing config:
plugin.tx_solr.index.queue.[table] = 1must exist for each table - Records outside siteroot: Set
additionalPageIdsand enable "tracking of records outside siteroot" in Extension Settings
Items in Queue but Not Indexed
- Scheduler not running: A "Index Queue Worker" scheduler task must be configured and executing
- Check errors: Inspect the
errorscolumn:SELECT item_type, errors, COUNT(*) as cnt FROM tx_solr_indexqueue_item WHERE errors != '' GROUP BY item_type, errors; - Forced webroot: If TYPO3 cannot auto-detect the web root (CLI context), configure "Forced webroot" in the scheduler task settings
- Memory: Large content can exceed PHP
memory_limit. Increase for the scheduler process.
Items Indexed but Wrong/Missing Content
- Check the actual document in Solr Admin UI:
q=*:*&fq=type:tx_news_domain_model_news&rows=5&fl=uid,title,content,url,siteHash - Field mapping: Verify
plugin.tx_solr.index.queue.[table].fieldsTypoScript matches your Solr schema fields - HTML in content: Use
SOLR_CONTENTto strip HTML from RTE fields - Relations empty: Use
SOLR_RELATIONwithmultiValue = 1for category/tag fields
Re-indexing
- Soft re-index: Backend module -> clear queue -> re-queue -> run scheduler task
- Force re-index: Scheduler task "Force Re-Indexing of a site" for specific configurations
- Nuclear option (DDEV only):
TRUNCATE tx_solr_indexqueue_item;then re-initialize via backend module
12.4 Search / Query Problems
No Results in Frontend
graph LR
Plugin["Search Plugin<br/>(Fluid)"] --> TS["TypoScript Config"]
TS --> SolrQ["Solr Query<br/>(HTTP GET)"]
SolrQ --> Response["Solr Response<br/>(JSON)"]
Response --> Render["Fluid Rendering"]
- targetPage:
plugin.tx_solr.search.targetPagemust point to the page containing the search plugin - Disabled: Check
plugin.tx_solr.enabledis not set to0 - Site hash mismatch: Documents indexed with wrong site hash won't appear. Test in Solr Admin:
q=YOUR_TERM&fq=siteHash:YOUR_EXPECTED_HASH - Access restrictions: Documents have an
accessfield. Ensure frontend user group matches.
Wrong Results / Bad Relevance
- debugQuery: In Solr Admin, run
q=YOUR_TERM&debugQuery=trueto see score breakdowns - EXT:solrdebugtools: Install for Debug\Query ViewHelper, Explain functionality, and score pie charts
- Boosting: Configure field weights:
plugin.tx_solr.search.query { boostFunction = recip(ms(NOW,created),3.16e-11,1,1) boostQuery = type:pages^2.0 }
Facets Not Showing
plugin.tx_solr.search.faceting = 1missing- Field not indexed (check dynamic field suffix matches schema)
initializeWithEmptyQuery = 1needed to show facets before any search- Facet field has zero values in indexed documents
12.5 Logging Deep Dive
For the full logging setup (TypoScript config, FileWriter,
tail -f), see 12.0 Layer 2 above.
Selective logging per Index Queue config:
You can enable logging for just one config to reduce noise:
plugin.tx_solr.logging {
indexing = 0
indexing.queue.events = 1
}
This logs only the "events" queue processing, not "news" or "pages".
What the indexing log contains per document:
[DEBUG] Indexing document: tx_news_domain_model_news:42
Fields: title=My Event, content=...(truncated), url=/events/my-event
Solr response: {"responseHeader":{"status":0,"QTime":3}}
If indexing fails, you'll see the Solr error response:
[ERROR] Indexing failed for tx_news_domain_model_news:42
Error: Document is missing mandatory uniqueKey field: id
What the query log contains:
[DEBUG] Solr query: q=test+search&fq=siteHash:abc123&fl=*,score
[DEBUG] Solr raw GET: http://solr:8983/solr/core_en/select?q=test+search&...
[DEBUG] Search words: ["test", "search"]
[DEBUG] Response: 15 results in 3ms
Warning: Disable logging on production. Log data grows quickly and impacts performance. Remove the
plugin.tx_solr.loggingconfig and thewriterConfigurationfromadditional.php.
12.6 Solr Admin UI as Debugging Tool
See 12.0 Layer 5 for CLI
curlequivalents of all these queries.
Key diagnostic queries (run in Solr Admin -> Query tab):
| Query | Purpose |
|---|---|
q=*:*&rows=0 |
Total document count |
q=*:*&fq=type:pages&rows=5&fl=uid,title,url,siteHash |
Check indexed pages |
q=*:*&fq=type:tx_news_domain_model_news&fl=uid,title,content |
Check custom records |
q=YOUR_TERM&debugQuery=true |
Score explanation (see 12.0 Layer 3 for reading the output) |
q=*:*&facet=true&facet.field=type&rows=0 |
Document type distribution |
q=*:*&fq=siteHash:YOUR_HASH&rows=0 |
Documents per site hash |
Additional useful queries:
# Find a specific record by UID and type
q=*:*&fq=uid:42&fq=type:tx_news_domain_model_news&fl=*
# Check which fields exist on a document (all fields)
q=*:*&rows=1&fl=*
# Compare what two configs indexed
q=*:*&fq=indexing_configuration:news&rows=0
q=*:*&fq=indexing_configuration:events&rows=0
# Find documents with empty content
q=*:*&fq=-content:[* TO *]&fl=uid,title,type
# Check the access field (frontend user restrictions)
q=*:*&fl=uid,title,access&rows=10
Core Admin: Reload a core after schema changes (Core Admin -> Reload button, or via API: curl http://solr:8983/solr/admin/cores?action=RELOAD&core=core_en).
Analysis screen: Test how text is tokenized and stemmed. In Solr Admin -> Analysis, select the field type (e.g., text), paste text in the "Field Value (Index)" box, and click "Analyse Values". This shows every processing step: char filters, tokenizer, token filters (lowercase, stop words, stemming).
12.7 Database-Level Debugging
See 12.0 Layer 6 for the complete SQL query cookbook.
Quick reference:
-- Overview: what's in the queue?
SELECT item_type, indexing_configuration,
COUNT(*) as total,
SUM(CASE WHEN errors != '' THEN 1 ELSE 0 END) as errors
FROM tx_solr_indexqueue_item
GROUP BY item_type, indexing_configuration;
-- What went wrong?
SELECT uid, item_type, item_uid, errors, changed, indexed
FROM tx_solr_indexqueue_item
WHERE errors != ''
LIMIT 20;
-- Event queue (delayed monitoring mode)
SELECT * FROM tx_solr_eventqueue_item
ORDER BY tstamp DESC LIMIT 20;
-- Items that need re-indexing
SELECT uid, item_type, item_uid, changed, indexed
FROM tx_solr_indexqueue_item
WHERE changed > indexed
LIMIT 20;
-- Reset errors to retry indexing
UPDATE tx_solr_indexqueue_item SET errors = '' WHERE errors != '';
12.8 Common Issues Quick Reference
| Symptom | Likely Cause | Fix |
|---|---|---|
| "Search is currently not available" | Solr connection misconfigured | Check site config, initialize connection |
| No results for any query | Site hash mismatch | Check siteHashStrategy, re-index |
| Empty Index Queue | TypoScript not loaded on root page | Include EXT:solr static templates |
| Queue items never indexed | Scheduler task not running | Create "Index Queue Worker" task |
| Items in queue with errors | Indexing failure (memory, URL) | Check errors column in DB |
| Facets not visible | faceting = 0 or empty field |
Enable faceting, check field has values |
| Suggest not working | suggest = 0 or jQuery missing |
Enable suggest, check JS includes |
| Wrong content in results | Field mapping incorrect | Fix TypoScript fields config |
| Access-restricted pages missing | linkAccessRestrictedPages not set |
Add config.typolinkLinkAccessRestrictedPages = 1 |
| URL generation fails for records | detailPid not configured |
Set the correct detail page ID |
Docker: permission denied on /var/solr |
Volume ownership wrong | Ensure UID 8983 owns the volume |
| DDEV: port 8984 not reachable | Addon not installed or Solr down | ddev add-on get ddev/ddev-typo3-solr && ddev restart |
| Configset version mismatch | Wrong configset deployed | Redeploy matching ext_solr_13_1_0 |
| Jar loading error (Solr 9.8+) | CVE-2025-24814 migration needed | Move typo3lib/ to server root, or pull Docker 13.0.1+ |
| Tika: connection timeout | Tika Server not running or wrong port | Check Docker container, test connection in extension settings |
| Language core missing | No core for language | Create core with matching language schema |
13. Testing
Functional Test Setup
<?php
declare(strict_types=1);
namespace MyVendor\MyExt\Tests\Functional;
use ApacheSolrForTypo3\Solr\Tests\Integration\IntegrationTestCase;
final class CustomIndexingTest extends IntegrationTestCase
{
protected array $testExtensionsToLoad = [
'apache-solr-for-typo3/solr',
'my-vendor/my-ext',
];
public function testNewsRecordIsIndexed(): void
{
$this->importCSVDataSet(__DIR__ . '/Fixtures/news_records.csv');
$this->indexQueue('news');
$searchResult = $this->searchInSolr('test news title');
self::assertGreaterThan(0, $searchResult->getResultCount());
}
}
Testing Custom Event Listeners
<?php
declare(strict_types=1);
namespace MyVendor\MyExt\Tests\Unit\EventListener;
use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentsAreIndexedEvent;
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
use MyVendor\MyExt\EventListener\EnrichSolrDocuments;
use PHPUnit\Framework\TestCase;
final class EnrichSolrDocumentsTest extends TestCase
{
public function testCustomScoreIsAdded(): void
{
$document = new Document();
$document->setField('type', 'pages');
$event = $this->createMock(BeforeDocumentsAreIndexedEvent::class);
$event->method('getDocuments')->willReturn([$document]);
$listener = new EnrichSolrDocuments();
$listener($event);
self::assertNotNull($document->getField('custom_score_floatS'));
}
}
14. Best Practices
Production Checklist
- Correct configset version deployed (
ext_solr_13_1_0) - Solr JVM memory configured (
SOLR_JAVA_MEM=-Xms512m -Xmx1g) - Solr NOT publicly accessible (firewall, reverse proxy)
- Scheduler task "Index Queue Worker" running regularly
- Logging disabled in production
- Solr data volume backed up
- CVE patches applied (Solr 9.8.0+ jar migration)
-
siteHashStrategy = 1(site-identifier)
Security
- Solr must never be publicly accessible. Use firewall rules or bind to
127.0.0.1 - Use a reverse proxy (nginx/Apache) with authentication if remote access is needed
- Monitor Apache Solr security advisories
- Keep Tika Server updated (CVE-2025-54988: requires 3.2.2+)
Performance
- Commit strategy: Use
autoSoftCommit(1-5 seconds) for near-real-time,autoCommit(15-60 seconds) for durability - Cache warming: Configure
newSearcherandfirstSearcherevents insolrconfig.xml - Segment merging: Monitor segment count in Solr Admin, consider
optimizeduring off-peak
Multi-Site / Multi-Language
- One Solr core per language (e.g.,
core_en,core_de) with language-specific schema - Each TYPO3 site configures its own Solr connection in site config YAML
- Use
siteHashto isolate documents per site
Environment Configuration
Use helhum/dotenv-connector to manage Solr credentials per environment:
.envfile per environment (local, staging, production) withSOLR_HOST,SOLR_PORT, etc.- Never hardcode connection details in site config YAML committed to VCS
- Commit
.env.examplewith placeholder values
15. Database Structure
| Table | Purpose |
|---|---|
tx_solr_indexqueue_item |
Items queued for indexing (type, uid, errors, timestamps) |
tx_solr_indexqueue_indexing_property |
Additional indexing properties per queue item |
tx_solr_last_searches |
Recently executed search terms |
tx_solr_statistics |
Search statistics (terms, result counts, timestamps) |
tx_solr_eventqueue_item |
Event queue for delayed monitoring mode |
Key columns in tx_solr_indexqueue_item:
| Column | Type | Purpose |
|---|---|---|
item_type |
varchar | Table name (e.g., pages, tx_news_domain_model_news) |
item_uid |
int | Record UID |
indexing_configuration |
varchar | TypoScript configuration name |
changed |
int | Timestamp when record was last changed |
indexed |
int | Timestamp when record was last indexed |
errors |
text | Error message from last indexing attempt |
FAQ / Common Recipes
One Table, Two Index Configs (e.g. News + Events from EXT:news)
A common pattern: EXT:news is installed once but serves two purposes -- classic news articles and an event calendar (or product announcements, press releases, etc.). You want them as separate facet values in search.
Step 1: Two Index Queue configurations for the same table
Use additionalWhereClause to split records by category, type field, or sysfolder PID:
plugin.tx_solr.index.queue {
# Config 1: Classic news articles
news = 1
news {
type = tx_news_domain_model_news
additionalWhereClause = categories.uid IN (1,2,3)
fields {
title = title
content = SOLR_CONTENT
content.cObject = COA
content.cObject {
10 = TEXT
10.field = bodytext
}
category_stringM = SOLR_RELATION
category_stringM {
localField = categories
multiValue = 1
}
url = TEXT
url {
typolink.parameter = {$plugin.tx_news.settings.detailPid}
typolink.additionalParams = &tx_news_pi1[controller]=News&tx_news_pi1[action]=detail&tx_news_pi1[news]={field:uid}
typolink.additionalParams.insertData = 1
typolink.returnLast = url
}
# Custom field to distinguish in facets
newstype_stringS = TEXT
newstype_stringS.value = News
}
}
# Config 2: Events (same table, different records)
events = 1
events {
type = tx_news_domain_model_news
additionalWhereClause = categories.uid IN (4,5,6)
fields {
title = title
content = SOLR_CONTENT
content.cObject = COA
content.cObject {
10 = TEXT
10.field = bodytext
}
category_stringM = SOLR_RELATION
category_stringM {
localField = categories
multiValue = 1
}
# Event-specific fields
eventdate_dateS = datetime
eventlocation_stringS = TEXT
eventlocation_stringS {
field = teaser
}
url = TEXT
url {
typolink.parameter = {$plugin.tx_news.settings.detailPid}
typolink.additionalParams = &tx_news_pi1[controller]=News&tx_news_pi1[action]=detail&tx_news_pi1[news]={field:uid}
typolink.additionalParams.insertData = 1
typolink.returnLast = url
}
newstype_stringS = TEXT
newstype_stringS.value = Event
}
}
}
Alternative split strategies (use only one):
| Strategy | additionalWhereClause |
When to use |
|---|---|---|
| By category | categories.uid IN (4,5,6) |
Different category trees for news vs events |
| By sysfolder | pid IN (50,51) |
News and events stored in separate sysfolders |
| By type field | tx_news_domain_model_news.type = 1 |
EXT:news type field distinguishes record types |
| By tag | tags.uid = 10 |
Tagged with a specific tag |
Step 2: Facet on the custom field
plugin.tx_solr.search.faceting {
facets {
newstype {
label = Content Type
field = newstype_stringS
}
}
}
This shows "News (42)" and "Event (15)" as facet options.
Step 3: Initialize both queue configs
In the EXT:solr backend module -> Index Queue tab, both "news" and "events" appear as separate indexing configurations. Select both and click "Queue selected content".
How it works internally: Each config creates its own tx_solr_indexqueue_item entries with indexing_configuration = 'news' or indexing_configuration = 'events'. The Scheduler worker processes both. In Solr, the documents have type = tx_news_domain_model_news (the table name) but differ in the newstype_stringS field.
Tip: If you want the Solr
typefield itself to differ (e.g.,type = newsvstype = events), you can override it in the fields config:type = TEXT/type.value = events. Be aware this affects other type-based filtering.
Different Detail Page URLs per Config
When news and events have separate detail pages:
plugin.tx_solr.index.queue.news.fields.url {
typolink.parameter = 42
# ...
}
plugin.tx_solr.index.queue.events.fields.url {
typolink.parameter = 55
# ...
}
Three or More Configs from One Table
The pattern scales -- you can create as many Index Queue configs as needed for the same table. Each gets its own name, additionalWhereClause, field mappings, and detail page URL.
Related Skills
- typo3-datahandler: Database operations that trigger Solr indexing
- typo3-seo: SEO configuration that complements Solr search
- ai-search-optimization: AEO/GEO for AI search visibility
- typo3-content-blocks: Content Elements that can be indexed
- typo3-testing: Testing patterns for TYPO3 extensions
References
Official Documentation
- EXT:solr Documentation
- EXT:tika Documentation
- Version Matrix
- Dynamic Fields Reference
- tx_solr.search Reference
- tx_solr.index Reference
- tx_solr.suggest Reference
- Extension Settings
- PSR-14 Events
- Indexing (Development)
- Index Queue
- Facets
- Autosuggest
- AJAX Results
- Customize Frontend
- Fluid Structure
- Scheduler Tasks
- FAQ
GitHub Repositories
Apache Solr Server
- Reference Guide
- Dense Vector Search
- Text to Vector (LLM Module)
- Solr Modules
- LLM API Javadoc
- Tutorial: Vectors
Commercial & Hosting
- typo3-solr.com
- Changelog EXT:solr
- Changelog EXT:solrfal
- hosted-solr.com
- hosted-solr.com Pricing
- dkd Enterprise Support
- dkd Workshops
DDEV
Mittwald
Environment Config
Community
Blogs & Tutorials
- Sease: Neural Search
- Sease: KNN Benchmark
- Sease: Semantic Search Text-to-Vector
- Sease: SeededKnnVectorQuery (Solr 10)
- LangChain4j Embedding Models
v14-Only Solr Changes
The following Solr-related changes are relevant when running EXT:solr on TYPO3 v14.
EXT:solr v14 Compatibility [v14 only]
EXT:solr v14 compatibility is being developed on the task/14LTS_compatibility branch. Check Packagist for the latest v14-compatible release.
Fluid 5.0 in Search Templates [v14 only]
Search result templates and facet partials must comply with Fluid 5.0 strict typing:
- ViewHelper arguments are strictly typed.
- No underscore-prefixed template variables.
- Review custom result and facet templates for type mismatches.
TypoScriptFrontendController Removal [v14 only]
Custom indexer or search plugins referencing $GLOBALS['TSFE'] must migrate to request attributes. This affects:
- Custom
IndexQueueInitializerimplementations - PSR-14 event listeners for
AfterSearchHasBeenExecutedEvent - Custom
ResultParserclasses
TCA searchFields Migration [v14 only]
If your custom record TCA uses ctrl.searchFields for Solr indexing configuration, note that searchFields is removed from ctrl in v14. Use per-column 'searchable' => true instead. This does not directly affect Solr Index Queue configuration (which uses its own field mapping), but may affect backend search behavior.
Credits & Attribution
- dkd Internet Service GmbH for developing and maintaining EXT:solr, EXT:tika, and the TYPO3-Solr ecosystem
- Apache Software Foundation for Apache Solr and Apache Tika
- b13 for maintaining the ddev-typo3-solr addon
- Helmut Hummel (helhum) for dotenv-connector
- Mittwald for managed Solr hosting documentation