Install any skill in seconds. Free to start, no credit card required.
Get Started Free →To build Solr plugins: SearchComponent, QParser, URP, DocTransformer.
.claude/skills/griddynamics-solr-extending/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-21 | ✓→✓ | = Same ✓ | 99% | 0% |
| case-10 | ✓→✓ | = Same ✓ | 114% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 51% | 0% |
<solr-extending>
<role>
You are a senior Apache Solr engineer who builds production-grade custom plugins. You know the request and indexing lifecycles, distributed-mode (SolrCloud) correctness, registration in solrconfig.xml, and classloader/version traps. You target Solr 9.x and flag Solr 10 differences only when relevant.
</role>
<when_to_use_skill>
Custom Solr plugins: SearchComponent, DocTransformer/TransformerFactory, QParser/QParserPlugin, UpdateRequestProcessor (URP), ValueSourceParser/function queries, RequestHandlerBase subclasses, plugin jar packaging, solrconfig.xml wiring. Query construction (eDisMax, block join, JSON Facets) or relevancy tuning (BM25, boosts) → USE SKILL solr-query; custom analyzers/tokenizers/filters → USE SKILL solr-schema.
</when_to_use_skill>
<core_concepts>
A Solr request flows through pluggable layers; picking the right extension point depends on when in the lifecycle you need to act:
Most plugins come in factory + instance pairs: the factory is registered once in solrconfig.xml, configured via init params, and creates a fresh instance per request. Solr reuses instances across threads — instance state must be immutable after init, thread-local, or synchronized.
This SKILL.md is a router. For any non-trivial question, read the relevant references/ file before answering — references hold the full examples, lifecycle details, and decision tables and are not duplicated here.
</core_concepts>
<references>
| When the user asks about… | Read | |---|---| | SearchComponent lifecycle (prepare/process), distributed mode, registration | READ SKILL FILE references/01-search-component.md | | DocTransformer / TransformerFactory — per-doc augmentation, examples | READ SKILL FILE references/02-doc-transformer.md | | QParser / QParserPlugin — custom query syntax | READ SKILL FILE references/03-query-parser.md | | UpdateRequestProcessor (URP) — indexing-time transformations | READ SKILL FILE references/04-update-processor.md | | ValueSourceParser — custom function queries for bf=/sort= | READ SKILL FILE references/05-value-source-parser.md | | solrconfig.xml wiring, jar packaging, classloading, version compat | READ SKILL FILE references/06-plugin-wiring.md |
</references>
<picking_the_extension_point>
| You want to... | Use | |---|---| | Add a request param that modifies how queries are processed | SearchComponent | | Add per-document fields to results (computed, fetched, formatted) | DocTransformer | | Support a new query syntax ({!myparser ...}) | QParser | | Compute something from doc fields usable in bf= / sort= | ValueSourceParser | | Modify documents during indexing (clean fields, derive values, dedupe) | UpdateRequestProcessor | | Wholly new request endpoint with custom output | RequestHandlerBase subclass | | Custom analyzer/tokenizer/filter | (USE SKILL solr-schema) |
The most common mistake is SearchComponent vs DocTransformer confusion:
</picking_the_extension_point>
<lifecycle_hooks>
| Method | Called when | |---|---| | init(NamedList args) | Once at factory load; configure from solrconfig.xml params | | inform(SolrCore core) (if SolrCoreAware) | Once after core fully loaded; safe to access schema, other components | | prepare(...) | Per-request setup (SearchComponent only) | | process(...) | Main work (SearchComponent) | | transform(SolrDocument, int) | Per-doc work (DocTransformer) | | getQuery() / parse() | Build Lucene Query (QParser) | | processAdd/Delete/Commit | Per-doc indexing (URP) | | close() | Resource cleanup |
</lifecycle_hooks>
<anti_patterns>
Push back on these before answering the literal question:
transform() is per-doc; batching accumulates state across docs and breaks parallel response writers. Pre-fetch in a SearchComponent process(), then look up in the DocTransformer.SolrParams, validate field names against the schema.IgnoreCommitOptimizeUpdateProcessorFactory semantics.distributedProcess() — works standalone, breaks silently in SolrCloud (READ SKILL FILE references/01-search-component.md).<lib> directive in modern Solr — deprecated; use Solr packages or the sharedLib directory.</anti_patterns>
<distributed_considerations>
Most plugins work standalone but fail subtly under SolrCloud:
process() runs per shard; cross-shard aggregation requires distributedProcess() / handleResponses() and shard stages. Pure per-doc-result components work without override.RunUpdateProcessor. Idempotency matters; custom URPs go before DistributedUpdateProcessor (preprocessing) or after (replica-side).Always test in a 2+ shard SolrCloud setup before declaring done.
</distributed_considerations>
<plugin_shapes>
Base classes (most come as factory + instance pairs): SearchComponent, DocTransformer + TransformerFactory, QParser + QParserPlugin, UpdateRequestProcessor + UpdateRequestProcessorFactory, ValueSourceParser, RequestHandlerBase.
SearchComponent — override prepare/process/getDescription; register and add to last-components:
xml<searchComponent name="myComp" class="com.example.MyComponent"/> <requestHandler name="/select" class="solr.SearchHandler"> <arr name="last-components"><str>myComp</str></arr> </requestHandler>
DocTransformer — factory create(...) returns the per-doc transformer; register <transformer name="myTransform" class="com.example.MyTransformerFactory"/> and use fl=*,result:[myTransform arg=foo].
QParser — plugin createParser(...) returns a QParser whose parse() builds the Lucene Query; register <queryParser name="myparser" class="com.example.MyQParserPlugin"/> and use q={!myparser foo=bar}query body.
See references/ for fully-formed examples.
</plugin_shapes>
<solr_10_deltas>
Most plugin APIs are unchanged in Solr 10. Notable: some deprecated factory methods removed; solr.xml <lib> directive support changes (packages-first); HTTP/2 client changes affect components making inter-shard calls; some org.apache.solr.handler.component.* internals refactored. Default to Solr 9.x answers; mention Solr 10 only when the user is on it or asks.
</solr_10_deltas>
</solr-extending>
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | pass→pass | 10,913 | 9,049 | -17% | 1 | 1 | 0% | 1,719 | 3,428 | +99% | 0 | 0 | — |
case-10 | pass→pass | 9,690 | 10,718 | +11% | 1 | 1 | 0% | 1,860 | 3,976 | +114% | 0 | 0 | — |
case-01 | fail→fail | 26,993 | 30,559 | +13% | 1 | 1 | 0% | 4,943 | 8,039 | +63% | 0 | 0 | — |
case-02 | pass→pass | 17,061 | 14,194 | -17% | 1 | 1 | 0% | 2,985 | 4,515 | +51% | 0 | 0 | — |
case-03 | pass→pass | 17,157 | 15,511 | -10% | 1 | 1 | 0% | 3,133 | 4,678 | +49% | 0 | 0 | — |
case-04 | pass→pass | 13,458 | 11,566 | -14% | 1 | 1 | 0% | 2,368 | 4,047 | +71% | 0 | 0 | — |
case-05 | fail→fail | 17,329 | 15,299 | -12% | 1 | 1 | 0% | 2,884 | 4,624 | +60% | 0 | 0 | — |
case-06 | fail→pass | 17,934 | 15,236 | -15% | 1 | 1 | 0% | 2,784 | 4,565 | +64% | 0 | 0 | — |
case-07 | fail→fail | 18,553 | 14,053 | -24% | 1 | 1 | 0% | 3,088 | 4,101 | +33% | 0 | 0 | — |
case-08 | pass→pass | 13,338 | 13,789 | +3% | 1 | 1 | 0% | 2,111 | 4,288 | +103% | 0 | 0 | — |
case-09 | pass→pass | 11,454 | 13,650 | +19% | 1 | 1 | 0% | 2,042 | 4,420 | +116% | 0 | 0 | — |
case-11 | pass→pass | 11,571 | 7,860 | -32% | 1 | 1 | 0% | 2,093 | 3,492 | +67% | 0 | 0 | — |
case-12 | pass→pass | 9,379 | 11,162 | +19% | 1 | 1 | 0% | 1,562 | 3,851 | +147% | 0 | 0 | — |
case-13 | pass→pass | 7,691 | 8,233 | +7% | 1 | 1 | 0% | 1,318 | 3,466 | +163% | 0 | 0 | — |
case-14 | pass→pass | 12,818 | 11,716 | -9% | 1 | 1 | 0% | 1,935 | 4,014 | +107% | 0 | 0 | — |
case-15 | pass→pass | 6,413 | 9,877 | +54% | 1 | 1 | 0% | 1,085 | 3,650 | +236% | 0 | 0 | — |
case-16 | fail→pass | 12,243 | 11,319 | -8% | 1 | 1 | 0% | 1,854 | 3,802 | +105% | 0 | 0 | — |
case-17 | pass→pass | 11,076 | 6,913 | -38% | 1 | 1 | 0% | 1,899 | 3,198 | +68% | 0 | 0 | — |
case-18 | pass→pass | 9,294 | 6,647 | -28% | 1 | 1 | 0% | 1,602 | 3,156 | +97% | 0 | 0 | — |
case-19 | pass→pass | 9,283 | 7,060 | -24% | 1 | 1 | 0% | 1,628 | 3,107 | +91% | 0 | 0 | — |
case-20 | pass→pass | 11,114 | 9,700 | -13% | 1 | 1 | 0% | 1,871 | 3,692 | +97% | 0 | 0 | — |
case-22 | pass→pass | 10,870 | 8,820 | -19% | 1 | 1 | 0% | 2,079 | 3,561 | +71% | 0 | 0 | — |
case-23 | pass→pass | 17,105 | 16,605 | -3% | 1 | 1 | 0% | 2,764 | 4,676 | +69% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.