Vector Search Schema Reference - Fields, Type Suffixes & Text Analysis

The schema behind the crawler, the modules and the packages, field by field

Vector Search Schema Reference

Every Opensolr index is a real Apache Solr core with its own schema.xml, and that file is yours: you can edit it or replace it with any schema Solr accepts. This page documents one specific schema: the generic vector search schema that Opensolr ships on vector-enabled indexes, and that all Opensolr data tools write into.

It is the schema behind the Web Crawler, the Data Ingestion API, the Drupal module, the WordPress plugin, the LangChain, LlamaIndex, Haystack, Laravel Scout and MCP packages, and the hosted search page. If you use any of those, this is your schema, and the page explains every field, every type suffix and every analyser in it. If you brought your own configset, your schema is whatever you made it, and this page is a reference for what the Opensolr tools expect.

Not the only schema you can have

Open your index in the control panel and go to Configuration: the schema.xml you see there is the live file for that index, and you can change it or upload a whole configset of your own. Indexes created with a custom configset, or before the vector schema existed, will look different from this page. Keep in mind that the crawler, the modules, the packages and the hosted search page read the fields described here by name; a custom schema that drops them loses those tools.

How to read a Solr schema

A schema has three kinds of entries. A field is a named column with a type. A dynamicField is a name pattern such as *_f: any field whose name matches the pattern is created on the fly with that type. A fieldType defines how values are stored and, for text, how they are broken into searchable terms.

Each field carries a few switches. They decide what you can do with it:

indexed
The value is searchable and filterable. og_image is the only content field with indexed=false: it is returned in results but you cannot search it.
stored
The original value is kept and returned in results. Fields used only for matching (spell, the autocomplete fields, embeddings) are stored=false to save disk.
docValues
A column-oriented copy of the value for fast sorting, faceting and function queries. Every string, number and date field in this schema has it. Text fields cannot.
multiValued
The field can hold a list. Suffixes ending in m (_sm, _fm, _im, _dtm) are multi-valued; their single-letter siblings hold exactly one value.
termVectors
Positions and offsets kept per term, so highlighting is fast and phrase-accurate. On by default for uri, title, description, text and every *_t field. Costs roughly 30 to 50 percent extra index size on those fields.

Relevance uses BM25, Solr's default under SchemaSimilarityFactory. Field weights on top of it are set in Search Tuning.

What you send, and what the pipeline adds

A document enters through the crawler or the ingestion API with four required fields: uri, title, description, text. Everything else is optional. Before the document reaches Solr, the pipeline derives the fields below. You never have to send them, and if you do, your value wins where the table says so.

Your document uri · title · description · text price_f · brand_sm · date_dt meta_* · anything_t Opensolr pipeline embeddings · sentiment · language autocomplete · spell dictionary string copies · dates · size Solr index keyword + vector search facets · ranges · sorting highlighting · spellcheck
FieldDerived fromYour value
idMD5 of the normalised uri (trailing slash removed). Re-sending the same uri updates the document instead of duplicating it.Ignored
uri_s, title_s, author_sExact-string copies of uri, title, author, for faceting and exact filtering.Overwritten
tags, title_tagsCleaned words from title, description and the first 3,000 characters of text, feeding the autocomplete fields.Overwritten
spellSame word list as tags, used to build the "did you mean" dictionary.Overwritten
embeddingsA 1,024-dimension vector of title + description + the first complete sentences of the text (or text_t when present). Skipped on plans without vector search.Ignored
sent_pos, sent_neu, sent_neg, sent_comSentiment scores of the same text.Overwritten
meta_detected_languageLanguage detected from title + description.Kept if you send one
creation_date, meta_creation_dateDerived from timestamp when you send it (unix epoch or any parseable date).Kept
sizeBytes of title + description + text.Overwritten
meta_domainHost of the uri, without www.Kept if you send one
content_typeDefaults to text/html; for rtf:true documents the real MIME type of the fetched file.Kept
category, og_image, meta_icon, meta_og_localeSet to an empty string when missing, so the search page never hits an absent field.Kept

Any other key in your document is passed to Solr unchanged. That is the whole trick of the type suffixes below: the pipeline does not map or rename anything, the schema pattern does.

Core fields

These fields exist on every document. Types refer to the field types explained further down.

Content

FieldTypeRole
idstringUnique key. MD5 of the URI.
uritext_generalThe document URL, searchable word by word. Highlighting enabled.
titletext_generalHighest default search weight. Highlighting enabled.
descriptiontext_generalSecond search weight, used for result snippets. Highlighting enabled.
texttext_generalThe full body. Largest field, lowest default weight. Highlighting enabled.
authortext_generalSearchable author name. Exact copy in author_s.

Metadata, time and numbers

FieldTypeRole
content_typestringMIME type (text/html, application/pdf, image/png). Facet and filter.
content_statusstringHTTP status recorded by the crawler.
signaturestringContent hash used for de-duplication.
categorystringFree category label. Facet and filter.
og_imagestringThumbnail URL. Stored, not searchable.
creation_datepdatePublish date. Range filters and freshness sorting.
timestampplongUnix epoch of the same moment. Fast numeric sorting.
rankpintCustom ranking value, default 0. Available to boost functions.
sizepintContent size in bytes.

Geo and sentiment

FieldTypeRole
coordslocationLatitude,longitude point for distance filters and sorting ({!geofilt}, geodist()).
lat, lonpdoubleThe same coordinates as plain numbers, default 0.0.
sent_pos, sent_neu, sent_negpdoublePositive, neutral and negative sentiment, 0 to 1.
sent_compdoubleCompound sentiment, -1 to 1. Filter with sent_com:[0.5 TO *] for clearly positive content.

Search-only fields (indexed, never returned)

FieldTypeRole
spelltextSpellDictionary for "did you mean" suggestions, wired to the spellcheck component in search.xml.
tags, title_tagsedgy_text_kwPrefix matching on the whole phrase. title_tags is the default autocomplete field.
tags_ws, title_tags_wsedgy_text_wsPrefix matching on each word separately.
embeddingsknn_vectorThe 1,024-dimension vector behind semantic and hybrid search. Not stored: it cannot be read back, only searched.
_version_, _root_plong, stringSolr internals for optimistic concurrency and nested documents. Leave them alone.

Dynamic fields: add any field with a suffix

You do not edit the schema to add a field. You name it with the right suffix and Solr creates it with the matching type the first time it sees it. The suffix decides everything: whether the value is searched as text or matched exactly, whether you can facet, sort and filter by range, and which widget the facet sidebar uses for it.

Suffix Solr type Example Facet Sort Range Full-text
*_sstringcurrency_s: "EUR"Listyesnoexact
*_smstring, multibrand_sm: ["Nike","Asics"]Listnonoexact
*_ssstring, multicolour_ss: ["red","blue"]Listnonoexact
*_ttext_generalspecs_t: "Gore-Tex upper"nononoyes
*_ipintstock_i: 42Slideryesyesno
*_impint, multisizes_im: [40,41,42]Slidernoyesno
*_lplongviews_l: 1284930112Slideryesyesno
*_fpfloatprice_f: 79.99Slideryesyesno
*_fmpfloat, multiprices_fm: [79.99,69.99]Slidernoyesno
*_dpdouble, multiweight_d: [0.245]Slidernoyesno
*_dtpdatedate_dt: "2026-01-15T10:30:00Z"Date rangeyesyesno
*_dtmpdate, multievents_dtm: [...]Date rangenoyesno
*_bbooleanin_stock_b: trueListyesnono
meta_*stringmeta_sku: "AX-100"Listyesnoexact
phonetic_*text_general_
phonetic
phonetic_titlenononosounds-like
ignored_*
*_nlp
ignored, multiignored_debugnononono

Rules worth knowing

Dates
Solr wants ISO 8601 in UTC: 2026-01-15T10:30:00Z. A bare 2026-01-15 or a unix epoch is rejected on a _dt field. Send the epoch in timestamp instead and the pipeline fills creation_date for you.
Longest pattern wins
When a name matches two patterns, Solr takes the longer one. meta_price_f matches meta_* (6 characters) before *_f (3), so it becomes a string and loses range filters. Keep type suffixes out of the meta_ namespace: use price_f.
No suffix, no field
A key that matches no pattern and no core field, for example price, is rejected by Solr and the document fails. There is no catch-all. Every custom field needs a suffix or the meta_ prefix.
One value or many
Sending a list to a single-valued field (brand_s: ["a","b"]) fails. Sending a single value to a multi-valued field is fine.
Sorting needs one value
Only single-valued fields sort predictably. _sm, _fm, _im, _dtm, _d are for faceting and filtering, not sort=.
Strings are exact
A _s or meta_ field matches the whole value, case-sensitively. If people should be able to search the text, send it in a _t field too, or fold it into text.
Types are fixed once created
The first document decides. If price_f has been indexed as a float you cannot later send "n/a" into it.

A product, start to finish

One JSON document through the ingestion API, and the queries the suffixes make possible.

{
  "uri":          "https://shop.example.com/products/trail-runner-x",
  "title":        "Trail Runner X",
  "description":  "Lightweight trail shoe with a Gore-Tex upper.",
  "text":         "Full product description, care instructions, reviews ...",
  "timestamp":    1768473000,
  "price_f":      129.0,
  "currency_s":   "EUR",
  "brand_sm":     ["Asics"],
  "sizes_im":     [40, 41, 42, 43],
  "in_stock_b":   true,
  "specs_t":      "Gore-Tex upper, Vibram sole, 245 g",
  "meta_sku":     "TRX-129"
}

What that buys you on the query side, with plain Solr parameters:

You wantParameter
Shoes between 100 and 150 eurofq=price_f:[100 TO 150]&fq=currency_s:EUR
Only in stock, cheapest firstfq=in_stock_b:true&sort=price_f asc
Brand counts for the sidebarfacet=true&facet.field=brand_sm
Anything with size 42fq=sizes_im:42
Added in the last 30 daysfq=creation_date:[NOW-30DAYS TO NOW]
Average price per brandjson.facet={brands:{type:terms,field:brand_sm,facet:{avg:"avg(price_f)"}}}
Look up by your own SKUfq=meta_sku:TRX-129

The hosted search page builds these for you from the Facet Filters configuration. Direct Solr access works with the connection details on your dashboard.

Field types and how text is analysed

A text field is never matched against the raw value. At index time the value is turned into a stream of normalised terms; at query time your words go through a near-identical chain, and the two streams are compared. That is why running finds run, café finds cafe, and a synonym you add in synonyms.txt works without reindexing.

text_general: the main search type

Used by uri, title, description, text, author and every *_t field. The index-time chain, in order:

  1. 01HTMLStripCharFilterRemoves any HTML tags left in the value.
  2. 02MappingCharFilterApplies mapping-ISOLatin1Accent.txt: é → e, ß → ss, before tokenising.
  3. 03ICUTokenizerSplits text into words using Unicode rules, so Chinese, Japanese, Thai and mixed-script text tokenise correctly.
  4. 04CJKWidthFilterNormalises full-width and half-width CJK characters.
  5. 05EnglishPossessiveFilterOpensolr's becomes Opensolr.
  6. 06ASCIIFoldingFilterSecond accent pass for anything the mapping file missed.
  7. 07StopFilterDrops the words listed in stopwords.txt (the, and, ...).
  8. 08WordDelimiterGraphFilterWi-Fi becomes wi, fi and wifi; iPhone15 becomes iphone, 15, iphone15. The original token is kept too. Words in protwords.txt are left untouched.
  9. 09LowerCaseFilterEverything lower-case.
  10. 10SnowballPorterFilterEnglish stemming: running, runs, runner share the stem run. protwords.txt exempts words from stemming.
  11. 11LengthFilterDrops tokens longer than 500 characters (base64 blobs, broken markup).
  12. 12RemoveDuplicatesTokenFilterCollapses tokens that ended up identical at the same position.

The query-time chain is the same with one addition: SynonymGraphFilter expands your words with synonyms.txt at query time only. Adding a synonym therefore takes effect on the next search, after a core reload, with no reindex. Both chains use the same stopword, protected-word and accent files, all editable under Configuration.

edgy_text_kw and edgy_text_ws: autocomplete

Edge n-gram types. At index time every term is expanded into its prefixes, 1 to 25 characters long: runner becomes r, ru, run, runn, runne, runner. At query time there is no n-gram step, so what you type is matched as an exact prefix. The difference between the two:

edgy_text_kw
KeywordTokenizer: the whole value is one token, then split by WordDelimiterGraphFilter. Non-alphanumerics are stripped. Best for matching the start of a full title. Fields: tags, title_tags.
edgy_text_ws
WhitespaceTokenizer: each word is its own token, so typing run matches Trail Runner on the second word. Fields: tags_ws, title_tags_ws.

These four fields also sit in the default search with a tiny weight (0.1), which is what makes partial words rank at all. See Search Tuning.

textSpell: the spellcheck dictionary

Deliberately minimal: StandardTokenizer and lower-casing, nothing else. No stemming, no stopwords, so the dictionary contains real words as they appear in your content. The spellcheck component in search.xml reads the spell field, allows up to 2 edits between the typed word and a suggestion, and only tries to correct words that occur in fewer than half of all documents: a word that common is assumed to be spelled right.

text_general_phonetic: sounds-like matching

Available for any phonetic_* field. After stemming, PhoneticFilter with the Double Metaphone encoder adds a phonetic code next to each term, so Smyth matches Smith and Kathryn matches Catherine. The crawler fills phonetic_title, phonetic_description and phonetic_text; the ingestion API leaves them empty. It roughly doubles the size of those fields, which is why it is opt-in.

knn_vector: the embedding

solr.DenseVectorField with vectorDimension="1024" and cosine similarity. The dimension is fixed by the embedding model Opensolr runs; a vector of any other length is rejected. Searched with {!knn f=embeddings topK=N} or, on Opensolr, through the {!hybrid} parser that fuses it with keyword scores. Details of the model and the API in AI & Vector Search.

Primitive and special types

TypeSolr classNotes
stringStrFieldExact value, case-sensitive, docValues on. Missing values sort last.
booleanBoolFieldtrue / false. Also accepts 1, t, T as true.
pint, plong, pfloat, pdouble*PointFieldPoint-based numerics, fast range queries. The legacy names int, tint, float, tfloat and so on map to the same classes for compatibility with old configs.
pdateDatePointFieldISO 8601 UTC. Supports date math: [NOW-7DAYS TO NOW], NOW/DAY.
locationLatLonPointSpatialField"lat,lon" strings. {!geofilt sfield=coords pt=44.43,26.10 d=25} filters within 25 km.
ignoredStrField, indexed=false, stored=falseAccepted and thrown away. Lets you send documents with extra keys without failing.

What is not in schema.xml

Copy fields
There are none. Classic Solr schemas use <copyField> to fill title_s from title; here the pipeline writes those copies itself. If you index through Solr's /update handler directly, bypassing the crawler and the ingestion API, you must send uri_s, title_s, tags, title_tags and spell, or those features stay empty for your documents.
Request handlers
/select, the spellcheck component and /autocomplete live in search.xml, included from solrconfig.xml. Default search field is text.
Field weights
Not in the schema. The search page and the API apply qf weights from Search Tuning at query time.
The hybrid parser
{!hybrid} is a Solr plugin installed on Opensolr clusters, registered in solrconfig.xml, not a schema entry.
Facet definitions
Which fields show up as sidebar filters, their labels and widgets, are stored in your index settings, not in Solr. See Facet Filters.

Changing the schema

The file is yours. Edit it under Configuration, save, and reload the core from Index Tools. A few things to keep in mind before you do:

  • Adding a field, a dynamic pattern or a field type is safe and takes effect on reload.
  • Changing the type of a field that already holds data requires a reindex: existing documents were analysed with the old type and will not match the new one.
  • Do not rename or remove the core fields. The crawler, the ingestion pipeline, the search page and the AI features read them by name.
  • vectorDimension must stay 1024 while you use Opensolr embeddings. Change it only if you index your own vectors from a different model.
  • Word lists (synonyms.txt, stopwords.txt, protwords.txt) change behaviour on reload. Synonyms apply at query time and need no reindex; stopwords and protected words affect index-time analysis, so a reindex makes them apply to old documents too.
  • If you moved to a managed-schema instead, the same fields and types apply; the file format differs. See managed-schema to schema.xml.

The file itself

This is the exact generic vector search schema.xml that Opensolr deploys on vector-enabled indexes, compatible with Solr 9.x. Everything explained above is in here. Scroll through it, or copy it as the starting point for a schema of your own.

449 lines, last updated 2 September 2026.

<?xml version="1.0" encoding="UTF-8"?>
<schema name="opensolr-crawler-solr9.x-optimized" version="2.1">

  <similarity class="solr.SchemaSimilarityFactory"/>
  
  <!-- 
  ============================================================================
  OPENSOLR OPTIMIZED SCHEMA
  ============================================================================
  
  DOCVALUES EXPLAINED:
  - docValues=true stores column-oriented data for fast sorting, faceting, grouping
  - Use for: fields you sort on, facet on, or use in function queries
  - Don't use for: large text fields (wastes disk space)
  - String/numeric fields: almost always benefit from docValues
  - Text fields: cannot use docValues (use for full-text search only)
  
  TERMVECTORS EXPLAINED:
  - termVectors=true enables highlighting, more-like-this, term statistics
  - termPositions=true needed for phrase highlighting
  - termOffsets=true needed for fast highlighting
  - storeOffsetsWithPositions=true enables even faster highlighting
  - Use for: fields you want to highlight or use for MLT
  - Cost: increases index size ~30-50%
  
  ============================================================================
  -->

  <!-- 
  ============================================================================
  UNIQUE KEY - Required
  ============================================================================
  -->
  <field name="id" type="string" indexed="true" stored="true" required="true" docValues="true"/>
  <uniqueKey>id</uniqueKey>

  <!-- 
  ============================================================================
  CORE CONTENT FIELDS - These are your main searchable fields
  ============================================================================
  -->
  
  <!-- URI: searchable URL, also used for deduplication -->
  <field name="uri" type="text_general" indexed="true" stored="true" 
         termVectors="true" termPositions="true" termOffsets="true" storeOffsetsWithPositions="true"/>
  
  <!-- Title: most important search field, heavily weighted -->
  <field name="title" type="text_general" indexed="true" stored="true" 
         termVectors="true" termPositions="true" termOffsets="true" storeOffsetsWithPositions="true"/>
  
  <!-- Description: second most important, used for snippets -->
  <field name="description" type="text_general" indexed="true" stored="true" 
         termVectors="true" termPositions="true" termOffsets="true" storeOffsetsWithPositions="true"/>
  
  <!-- Text: main body content, lowest weight but largest field -->
  <field name="text" type="text_general" indexed="true" stored="true" 
         termVectors="true" termPositions="true" termOffsets="true" storeOffsetsWithPositions="true"/>
  
  <!-- Author: searchable, may want to facet on this -->
  <field name="author" type="text_general" indexed="true" stored="true"/>

  <!-- 
  ============================================================================
  METADATA FIELDS - For filtering, faceting, sorting
  docValues=true on these for fast faceting/sorting
  ============================================================================
  -->
  
  <!-- Content type: filter by type (text/html, image/png, etc) -->
  <field name="content_type" type="string" indexed="true" stored="true" docValues="true"/>
  
  <!-- Content status: HTTP status codes, crawl status -->
  <field name="content_status" type="string" indexed="true" stored="true" docValues="true"/>
  
  <!-- Signature: content hash for deduplication -->
  <field name="signature" type="string" indexed="true" stored="true" docValues="true"/>
  
  <!-- Category: for categorization/faceting -->
  <field name="category" type="string" indexed="true" stored="true" docValues="true"/>
  
  <!-- OG Image: stored only, not searchable -->
  <field name="og_image" type="string" indexed="false" stored="true"/>

  <!-- 
  ============================================================================
  DATE/TIME FIELDS - For freshness sorting and filtering
  docValues=true essential for date range queries and sorting
  ============================================================================
  -->
  
  <!-- Creation date: when content was published/created -->
  <field name="creation_date" type="pdate" indexed="true" stored="true" docValues="true"/>
  
  <!-- Timestamp: unix timestamp, useful for sorting -->
  <field name="timestamp" type="plong" indexed="true" stored="true" docValues="true"/>

  <!-- 
  ============================================================================
  NUMERIC FIELDS - For scoring, filtering, sorting
  docValues=true for fast range queries and sorting
  ============================================================================
  -->
  
  <!-- Rank: page rank or custom scoring -->
  <field name="rank" type="pint" indexed="true" stored="true" docValues="true"/>
  
  <!-- Size: document size in bytes -->
  <field name="size" type="pint" indexed="true" stored="true" docValues="true" default="0"/>

  <!-- 
  ============================================================================
  GEOSPATIAL FIELDS - For location-based search
  ============================================================================
  -->
  
  <field name="coords" type="location" indexed="true" stored="true"/>
  <field name="lat" type="pdouble" indexed="true" stored="true" docValues="true" default="0.0"/>
  <field name="lon" type="pdouble" indexed="true" stored="true" docValues="true" default="0.0"/>

  <!-- 
  ============================================================================
  SENTIMENT FIELDS - For sentiment analysis filtering
  docValues=true for range queries (e.g., "show positive articles")
  ============================================================================
  -->
  
  <field name="sent_pos" type="pdouble" indexed="true" stored="true" docValues="true" default="0"/>
  <field name="sent_neu" type="pdouble" indexed="true" stored="true" docValues="true" default="0"/>
  <field name="sent_neg" type="pdouble" indexed="true" stored="true" docValues="true" default="0"/>
  <field name="sent_com" type="pdouble" indexed="true" stored="true" docValues="true" default="0"/>

  <!-- 
  ============================================================================
  SPELLCHECK FIELD - For "did you mean" suggestions
  Not stored, only indexed for spellcheck component
  ============================================================================
  -->
  
  <field name="spell" type="textSpell" indexed="true" stored="false" multiValued="true"/>

  <!-- 
  ============================================================================
  AUTOCOMPLETE / EDGE NGRAM FIELDS - For typeahead suggestions
  These create prefix-matching indexes
  ============================================================================
  -->
  
  <field name="tags" type="edgy_text_kw" indexed="true" stored="false"/>
  <field name="title_tags" type="edgy_text_kw" indexed="true" stored="false"/>
  <field name="tags_ws" type="edgy_text_ws" indexed="true" stored="false"/>
  <field name="title_tags_ws" type="edgy_text_ws" indexed="true" stored="false"/>

  <!-- 
  ============================================================================
  VECTOR EMBEDDINGS - For semantic/AI search
  stored=false saves space (embeddings are large), only need for search
  ============================================================================
  -->
  
  <field name="embeddings" type="knn_vector" indexed="true" stored="false"/>

  <!-- 
  ============================================================================
  DYNAMIC FIELDS - Flexible metadata without schema changes
  ============================================================================
  -->
  
  <!-- Generic meta fields: string type with docValues for faceting -->
  <dynamicField name="meta_*" type="string" indexed="true" stored="true" docValues="true"/>
  
  <!-- Brighteon-specific: text type for full-text search -->
  <dynamicField name="meta_brighteon_*" type="text_general" indexed="true" stored="true"/>
  
  <!-- Numeric dynamic fields -->
  <dynamicField name="*_d" type="pdouble" indexed="true" stored="true" docValues="true" multiValued="true"/>
  <dynamicField name="*_i" type="pint" indexed="true" stored="true" docValues="true"/>
  <dynamicField name="*_l" type="plong" indexed="true" stored="true" docValues="true"/>
  <dynamicField name="*_f" type="pfloat" indexed="true" stored="true" docValues="true"/>

  
  
  <!-- String dynamic fields with docValues -->
  <dynamicField name="*_sm" type="string" indexed="true" stored="true" docValues="true" multiValued="true"/>
  <dynamicField name="*_s"  type="string" indexed="true" stored="true" docValues="true"/>
  <dynamicField name="*_ss" type="string" indexed="true" stored="true" docValues="true" multiValued="true"/>
  
  <!-- Text dynamic fields for full-text search -->
  <dynamicField name="*_t" type="text_general" indexed="true" stored="true" 
                termVectors="true" termPositions="true" termOffsets="true"/>
  
  <!-- Date dynamic fields -->
  <dynamicField name="*_dt" type="pdate" indexed="true" stored="true" docValues="true"/>

  <dynamicField name="*_im" type="pint" indexed="true" stored="true" docValues="true" multiValued="true"/>
  <dynamicField name="*_fm" type="pfloat" indexed="true" stored="true" docValues="true" multiValued="true"/>
  <dynamicField name="*_dtm" type="pdate" indexed="true" stored="true" docValues="true" multiValued="true"/>
  
  <!-- Boolean dynamic fields -->
  <dynamicField name="*_b" type="boolean" indexed="true" stored="true" docValues="true"/>
  
  <!-- Phonetic search fields -->
  <dynamicField name="phonetic_*" type="text_general_phonetic" indexed="true" stored="false"/>
  
  <!-- Ignored fields - not indexed or stored -->
  <dynamicField name="ignored_*" type="ignored" multiValued="true"/>
  <dynamicField name="*_nlp" type="ignored" multiValued="true"/>

  <!-- 
  ============================================================================
  INTERNAL SOLR FIELDS - Required by Solr, don't modify
  ============================================================================
  -->
  
  <field name="_version_" type="plong" indexed="false" stored="false" docValues="true"/>
  <field name="_root_" type="string" indexed="true" stored="false" docValues="true"/>

  <!-- 
  ============================================================================
  ============================================================================
  FIELD TYPES
  ============================================================================
  ============================================================================
  -->
  
    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
        <analyzer type="index">
          <charFilter class="solr.HTMLStripCharFilterFactory"/>
          <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
          <tokenizer class="solr.ICUTokenizerFactory"/>
          <filter class="solr.CJKWidthFilterFactory"/>
          <filter class="solr.EnglishPossessiveFilterFactory"/>
          <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="false"/>
          <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
          <filter class="solr.WordDelimiterGraphFilterFactory"
                  generateWordParts="1" generateNumberParts="1"
                  catenateWords="1" catenateNumbers="1" catenateAll="1"
                  splitOnCaseChange="1" splitOnNumerics="1"
                  preserveOriginal="1" protected="protwords.txt"/>
          <filter class="solr.FlattenGraphFilterFactory"/>
          <filter class="solr.LowerCaseFilterFactory"/>
          <filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
          <filter class="solr.LengthFilterFactory" min="1" max="500"/>
          <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
        </analyzer>
        <analyzer type="query">
          <charFilter class="solr.HTMLStripCharFilterFactory"/>
          <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
          <tokenizer class="solr.ICUTokenizerFactory"/>
          <filter class="solr.CJKWidthFilterFactory"/>
          <filter class="solr.EnglishPossessiveFilterFactory"/>
          <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="false"/>
          <filter class="solr.SynonymGraphFilterFactory" ignoreCase="true" synonyms="synonyms.txt" expand="true"/>
          <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
          <filter class="solr.WordDelimiterGraphFilterFactory"
                  generateWordParts="1" generateNumberParts="1"
                  catenateWords="1" catenateNumbers="1" catenateAll="1"
                  splitOnCaseChange="1" splitOnNumerics="1"
                  preserveOriginal="1" protected="protwords.txt"/>
          <filter class="solr.LowerCaseFilterFactory"/>
          <filter class="solr.SnowballPorterFilterFactory" language="English" protected="protwords.txt"/>
          <filter class="solr.LengthFilterFactory" min="1" max="500"/>
          <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
        </analyzer>
  </fieldType>


  <!-- 
  ============================================================================
  TEXTSPELL - For spellcheck dictionary building
  Minimal analysis: just tokenize and lowercase
  ============================================================================
  -->
  
  <fieldType name="textSpell" class="solr.TextField" positionIncrementGap="100">
    <analyzer type="index">
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>
    <analyzer type="query">
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>
  </fieldType>

  <!-- 
  ============================================================================
  EDGY_TEXT_KW - Edge ngram with keyword tokenizer (whole field as one token)
  Use for: autocomplete on exact field content
  ============================================================================
  -->
  
  <fieldType name="edgy_text_kw" class="solr.TextField" positionIncrementGap="100">
    <analyzer type="index">
      <charFilter class="solr.HTMLStripCharFilterFactory"/>
      <tokenizer class="solr.KeywordTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/>
      <filter class="solr.WordDelimiterGraphFilterFactory" 
              generateWordParts="1" generateNumberParts="1"
              catenateWords="1" catenateNumbers="1" catenateAll="1"  splitOnNumerics="1"
              splitOnCaseChange="1" preserveOriginal="1" protected="protwords.txt"/>
      <filter class="solr.FlattenGraphFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
      <filter class="solr.PatternReplaceFilterFactory" pattern="([^a-z0-9])" replacement="" replace="all"/>
      <filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="25"/>
    </analyzer>
    <analyzer type="query">
      <charFilter class="solr.HTMLStripCharFilterFactory"/>
      <tokenizer class="solr.KeywordTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/>
      <filter class="solr.WordDelimiterGraphFilterFactory" 
              generateWordParts="1" generateNumberParts="1"
              catenateWords="1" catenateNumbers="1" catenateAll="1"
              splitOnCaseChange="1" preserveOriginal="1" protected="protwords.txt"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
      <filter class="solr.PatternReplaceFilterFactory" pattern="([^a-z0-9])" replacement="" replace="all"/>
      <!-- No EdgeNGram on query side - we want exact prefix match -->
    </analyzer>
  </fieldType>

  <!-- 
  ============================================================================
  EDGY_TEXT_WS - Edge ngram with whitespace tokenizer (each word separate)
  Use for: autocomplete on individual words
  ============================================================================
  -->
  
  <fieldType name="edgy_text_ws" class="solr.TextField" positionIncrementGap="100">
    <analyzer type="index">
      <charFilter class="solr.HTMLStripCharFilterFactory"/>
      <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/>
      <filter class="solr.WordDelimiterGraphFilterFactory" 
              generateWordParts="1" generateNumberParts="1"
              catenateWords="1" catenateNumbers="1" catenateAll="1"
              splitOnCaseChange="1" preserveOriginal="1" protected="protwords.txt"/>
      <filter class="solr.FlattenGraphFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
      <filter class="solr.PatternReplaceFilterFactory" pattern="([^a-z0-9])" replacement="" replace="all"/>
      <filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="25"/>
    </analyzer>
    <analyzer type="query">
      <charFilter class="solr.HTMLStripCharFilterFactory"/>
      <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/>
      <filter class="solr.WordDelimiterGraphFilterFactory" 
              generateWordParts="1" generateNumberParts="1"
              catenateWords="1" catenateNumbers="1" catenateAll="1"
              splitOnCaseChange="1" preserveOriginal="1" protected="protwords.txt"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
      <filter class="solr.PatternReplaceFilterFactory" pattern="([^a-z0-9])" replacement="" replace="all"/>
    </analyzer>
  </fieldType>

  <!-- 
  ============================================================================
  TEXT_GENERAL_PHONETIC - For fuzzy/phonetic matching
  Use for: "sounds like" search, name matching, typo tolerance
  WARNING: High index size cost, use sparingly
  ============================================================================
  -->
  
  <fieldType name="text_general_phonetic" class="solr.TextField" positionIncrementGap="100">
    <analyzer type="index">
      <charFilter class="solr.HTMLStripCharFilterFactory"/>
      <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
      <filter class="solr.WordDelimiterGraphFilterFactory"
              generateWordParts="1" generateNumberParts="1"
              splitOnCaseChange="1" splitOnNumerics="1"
              preserveOriginal="1"/>
      <filter class="solr.FlattenGraphFilterFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/>
      <filter class="solr.PorterStemFilterFactory"/>
      <filter class="solr.PhoneticFilterFactory" encoder="DoubleMetaphone" inject="true"/>
    </analyzer>
    <analyzer type="query">
      <charFilter class="solr.HTMLStripCharFilterFactory"/>
      <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
      <filter class="solr.WordDelimiterGraphFilterFactory" 
              generateWordParts="1" generateNumberParts="1"
              splitOnCaseChange="1" splitOnNumerics="1"
              preserveOriginal="1"/>
      <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/>
      <filter class="solr.PorterStemFilterFactory"/>
      <filter class="solr.PhoneticFilterFactory" encoder="DoubleMetaphone" inject="true"/>
    </analyzer>
  </fieldType>

  <!-- 
  ============================================================================
  KNN_VECTOR - Dense vector field for semantic search
  vectorDimension must match your embedding model output size
  similarityFunction: cosine is standard for normalized embeddings
  ============================================================================
  -->
  
  <fieldType name="knn_vector" class="solr.DenseVectorField" 
             vectorDimension="1024" 
             similarityFunction="cosine" />

  <!-- 
  ============================================================================
  PRIMITIVE TYPES - Using modern "p" prefix types (point-based)
  These are faster than the old Trie types
  ============================================================================
  -->
  
  <fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true"/>
  <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
  
  <!-- Point-based numeric types (Solr 7+, faster than Trie) -->
  <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
  <fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
  <fieldType name="plong" class="solr.LongPointField" docValues="true"/>
  <fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
  <fieldType name="pdate" class="solr.DatePointField" docValues="true"/>
  
  <!-- Legacy Trie types - keep for backward compatibility if needed -->
  <fieldType name="int" class="solr.IntPointField" docValues="true"/>
  <fieldType name="float" class="solr.FloatPointField" docValues="true"/>
  <fieldType name="long" class="solr.LongPointField" docValues="true"/>
  <fieldType name="double" class="solr.DoublePointField" docValues="true"/>
  <fieldType name="date" class="solr.DatePointField" docValues="true"/>
  <fieldType name="tint" class="solr.IntPointField" docValues="true"/>
  <fieldType name="tfloat" class="solr.FloatPointField" docValues="true"/>
  <fieldType name="tlong" class="solr.LongPointField" docValues="true"/>
  <fieldType name="tdouble" class="solr.DoublePointField" docValues="true"/>
  <fieldType name="tdate" class="solr.DatePointField" docValues="true"/>

  <!-- 
  ============================================================================
  SPECIAL TYPES
  ============================================================================
  -->
  
  <!-- Location for geo queries -->
  <fieldType name="location" class="solr.LatLonPointSpatialField" docValues="true"/>
  
  <!-- Ignored - for fields you want to accept but not index/store -->
  <fieldType name="ignored" class="solr.StrField" indexed="false" stored="false" multiValued="true"/>

</schema>

Related Documentation