Text Field
In AI search applications, vector search helps you find semantically similar entities, but the application often also needs the original source text behind each match. An LLM or agent can use that text as context to read, cite, summarize, or include the result in a prompt.
Zilliz Cloud provides the TEXT scalar field type for storing long source text directly with entities. Typical values include passages, long documents, article bodies, tickets, and logs. Unlike VARCHAR, which requires a fixed max_length, TEXT does not require you to set a maximum byte length in the collection schema.
To define a TEXT field in a collection schema, set datatype to DataType.TEXT.
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
- Zilliz CLI
schema.add_field(
field_name="content",
datatype=DataType.TEXT,
)
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
schema.addField(AddFieldReq.builder()
.fieldName("content")
.dataType(DataType.Text)
.enableAnalyzer(true)
.build());
// Note: DataType.Text is not supported in milvus-sdk-go as of client/v3.0.0.
// Note: DataType.Text is not supported in milvus-sdk-rust as of v3.0.2.
#include "milvus/MilvusClientV2.h"
milvus::FieldSchema content_field("content", milvus::DataType::TEXT);
content_field.EnableAnalyzer(true);
import { DataType } from "@zilliz/milvus2-sdk-node";
schema.push({
name: "content",
data_type: DataType.Text,
enable_analyzer: true
});
export contentField='{
"fieldName": "content",
"dataType": "Text",
"elementTypeParams": {
"enable_analyzer": true
}
}'
# Zilliz CLI
After the field is defined, each entity can include a string value in that field. You insert the value like other scalar fields and return it from query or search results by listing the field in output_fields.
TEXT fields support null values. To enable this feature, set nullable to True. For details, refer to Nullable Fields.
Limits
-
A
TEXTfield cannot be a primary field. Primary fields supportINT64andVARCHAR. -
TEXTfields do not supportPHRASE_MATCH. -
TEXTfields do not support default values. -
TEXTfields do not support scalar indexes. -
TEXTis not intended for regular metadata filtering. If you need to filter on short string metadata and the field value fits within theVARCHARlength limit, useVARCHAR. -
TEXTfields are not supported in external collections.
Choose TEXT or VARCHAR
TEXT and VARCHAR both store string values, but they support different application needs. Use VARCHAR for short, bounded metadata that identifies, categorizes, or filters entities. Use TEXT for longer source content that gives an LLM or agent enough context to read, cite, summarize, or build a prompt.
| Aspect | VARCHAR | TEXT |
|---|---|---|
| Best for | Short metadata used to identify, categorize, or filter entities, such as title, tag, category, external_id. | Longer source content used by LLM or agent workflows, such as content, passage, article_body, log_message. |
| Length setting | Requires max_length, which defines the maximum number of bytes the field can store. The maximum value is 65,535 bytes. If a value may exceed this limit, use TEXT. | Does not require max_length, so the schema does not need a fixed byte limit for the text value. |
| Storage behavior | Stores each value within the field's configured max_length. | Uses automatic storage selection for larger text values.. |
| Primary field support | Can be used as a primary field. | Cannot be used as a primary field. |
| Filtering | Use for short string metadata that needs to appear in filter expressions, such as category == "news" or tag in ["ai", "database"]. | Not intended for regular metadata filtering. |
For details about VARCHAR fields, refer to VARCHAR Field.
A common use of TEXT is Full Text Search with BM25. In this pattern, the TEXT field stores the original source content, and BM25 analyzes the text and generates sparse vectors for ranking keyword-based matches. Search results can then return the matched TEXT value as context for LLM or agent workflows. The following example shows how to use a TEXT field as the input field for BM25. To learn about Full Text Search concepts and query options, refer to Full Text Search.
Step 1: Create a collection with a TEXT field
The following example creates a collection with a TEXT field for source content and a sparse vector field for BM25-generated sparse vectors. The BM25 function converts the tokenized text from content into sparse vectors stored in sparse.
For BM25 full text search, the input TEXT field must set enable_analyzer=True.
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
- Zilliz CLI
from pymilvus import DataType, Function, FunctionType, MilvusClient
client = MilvusClient(uri="YOUR_CLUSTER_ENDPOINT")
COLLECTION_NAME = "text_bm25_collection"
if client.has_collection(COLLECTION_NAME):
client.drop_collection(COLLECTION_NAME)
schema = client.create_schema(auto_id=False, enable_dynamic_field=False)
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
schema.add_field(
field_name="content",
datatype=DataType.TEXT,
enable_analyzer=True,
)
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR)
bm25_function = Function(
name="content_bm25",
input_field_names=["content"],
output_field_names=["sparse"],
function_type=FunctionType.BM25,
)
schema.add_function(bm25_function)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import java.util.*;
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import io.milvus.v2.service.collection.request.DropCollectionReq;
ConnectConfig config = ConnectConfig.builder()
.uri("YOUR_CLUSTER_ENDPOINT")
.build();
MilvusClientV2 client = new MilvusClientV2(config);
String COLLECTION_NAME = "text_bm25_collection";
client.dropCollection(DropCollectionReq.builder()
.collectionName(COLLECTION_NAME)
.async(false)
.build());
CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
.build();
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("content")
.dataType(DataType.Text)
.enableAnalyzer(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("sparse")
.dataType(DataType.SparseFloatVector)
.build());
schema.addFunction(CreateCollectionReq.Function.builder()
.name("content_bm25")
.functionType(FunctionType.BM25)
.inputFieldNames(Collections.singletonList("content"))
.outputFieldNames(Collections.singletonList("sparse"))
.build());
// Note: DataType.Text is not supported in milvus-sdk-go as of client/v3.0.0.
// Note: DataType.Text is not supported in milvus-sdk-rust as of v3.0.2.
#include <iostream>
#include <memory>
#include "milvus/MilvusClientV2.h"
auto client = milvus::MilvusClientV2::Create();
milvus::ConnectParam connect_param{"YOUR_CLUSTER_ENDPOINT"};
auto status = client->Connect(connect_param);
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
}
const std::string COLLECTION_NAME = "text_bm25_collection";
milvus::CollectionSchemaPtr schema = std::make_shared<milvus::CollectionSchema>();
schema->SetEnableDynamicField(false);
schema->AddField(milvus::FieldSchema("id", milvus::DataType::INT64, "", true));
schema->AddField(milvus::FieldSchema("content", milvus::DataType::TEXT).EnableAnalyzer(true));
schema->AddField(milvus::FieldSchema("sparse", milvus::DataType::SPARSE_FLOAT_VECTOR));
auto function = std::make_shared<milvus::Function>("content_bm25", milvus::FunctionType::BM25);
function->AddInputFieldName("content");
function->AddOutputFieldName("sparse");
schema->AddFunction(function);
import { MilvusClient, DataType, FunctionType } from "@zilliz/milvus2-sdk-node";
const client = new MilvusClient({ address: "YOUR_CLUSTER_ENDPOINT" });
const COLLECTION_NAME = "text_bm25_collection";
if (await client.hasCollection({ collection_name: COLLECTION_NAME })) {
await client.dropCollection({ collection_name: COLLECTION_NAME });
}
const schema = [
{ name: "id", data_type: DataType.Int64, is_primary_key: true },
{ name: "content", data_type: DataType.Text, enable_analyzer: true },
{ name: "sparse", data_type: DataType.SparseFloatVector }
];
const functions = [{
name: "content_bm25",
type: FunctionType.BM25,
input_field_names: ["content"],
output_field_names: ["sparse"],
params: {}
}];
export BASE_URL="YOUR_CLUSTER_ENDPOINT"
export TOKEN="YOUR_CLUSTER_TOKEN"
export COLLECTION_NAME="text_bm25_collection"
# Drop an old copy if this example is run repeatedly.
curl --request POST \
--url "${BASE_URL}/v2/vectordb/collections/drop" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{\"collectionName\": \"${COLLECTION_NAME}\"}"
export schema='{
"fields": [
{"fieldName": "id", "dataType": "Int64", "isPrimary": true},
{"fieldName": "content", "dataType": "Text", "elementTypeParams": {"enable_analyzer": true}},
{"fieldName": "sparse", "dataType": "SparseFloatVector"}
],
"functions": [
{
"name": "content_bm25",
"type": "BM25",
"inputFieldNames": ["content"],
"outputFieldNames": ["sparse"]
}
]
}'
# Zilliz CLI
Step 2: Create a sparse vector index
Create an index on the sparse vector field generated by the BM25 function. The metric type must be set to BM25.
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
- Zilliz CLI
index_params = client.prepare_index_params()
index_params.add_index(
field_name="sparse",
index_type="SPARSE_INVERTED_INDEX",
metric_type="BM25",
params={
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75,
},
)
client.create_collection(
collection_name=COLLECTION_NAME,
schema=schema,
index_params=index_params,
)
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import java.util.*;
List<IndexParam> indexParams = new ArrayList<>();
Map<String, Object> bm25Params = new HashMap<>();
bm25Params.put("inverted_index_algo", "DAAT_MAXSCORE");
bm25Params.put("bm25_k1", 1.2);
bm25Params.put("bm25_b", 0.75);
indexParams.add(IndexParam.builder()
.fieldName("sparse")
.indexName("sparse_bm25_index")
.indexType(IndexParam.IndexType.SPARSE_INVERTED_INDEX)
.metricType(IndexParam.MetricType.BM25)
.extraParams(bm25Params)
.build());
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("text_bm25_collection")
.collectionSchema(schema)
.indexParams(indexParams)
.build();
client.createCollection(requestCreate);
// Note: DataType.Text is not supported in milvus-sdk-go as of client/v3.0.0.
// Note: DataType.Text is not supported in milvus-sdk-rust as of v3.0.2.
#include <iostream>
#include <utility>
#include <vector>
#include "milvus/MilvusClientV2.h"
std::vector<milvus::IndexDesc> indexes = {
milvus::IndexDesc("sparse", "sparse_bm25_index", milvus::IndexType::SPARSE_INVERTED_INDEX, milvus::MetricType::BM25)
};
status = client->CreateCollection(milvus::CreateCollectionRequest()
.WithCollectionName("text_bm25_collection")
.WithIndexes(std::move(indexes))
.WithCollectionSchema(schema));
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
}
const indexParams = [{
field_name: "sparse",
index_name: "sparse_bm25_index",
index_type: "SPARSE_INVERTED_INDEX",
metric_type: "BM25",
params: {
inverted_index_algo: "DAAT_MAXSCORE",
bm25_k1: 1.2,
bm25_b: 0.75
}
}];
const res = await client.createCollection({
collection_name: "text_bm25_collection",
schema: schema,
functions: functions,
index_params: indexParams
});
console.log(res);
export indexParams='[
{
"fieldName": "sparse",
"indexName": "sparse_bm25_index",
"indexType": "SPARSE_INVERTED_INDEX",
"metricType": "BM25",
"params": {
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
}
]'
curl --request POST \
--url "${BASE_URL}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 30" \
-d "{
\"collectionName\": \"text_bm25_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
# Zilliz CLI
Step 3: Insert TEXT data
Insert text directly into the TEXT field. Do not provide values for the sparse field. Milvus generates the sparse vectors internally by applying the BM25 function to content.
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
- Zilliz CLI
data = [
{
"id": 1,
"content": "Milvus stores vector embeddings and scalar fields in collections. It supports vector search, full text search, and metadata filtering for retrieval applications.",
},
{
"id": 2,
"content": "Long documents are often split into passages before embedding. Store each passage in a TEXT field so search results can return the source text.",
},
{
"id": 3,
"content": "Operational logs and support tickets often contain long natural-language text. TEXT fields can store these values without a fixed max_length setting.",
},
]
client.insert(collection_name=COLLECTION_NAME, data=data)
client.load_collection(collection_name=COLLECTION_NAME)
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
import java.util.Arrays;
import java.util.List;
Gson gson = new Gson();
List<JsonObject> rows = Arrays.asList(
gson.fromJson("{\"id\": 1, \"content\": \"Milvus stores vector embeddings and scalar fields in collections. It supports vector search, full text search, and metadata filtering for retrieval applications.\"}", JsonObject.class),
gson.fromJson("{\"id\": 2, \"content\": \"Long documents are often split into passages before embedding. Store each passage in a TEXT field so search results can return the source text.\"}", JsonObject.class),
gson.fromJson("{\"id\": 3, \"content\": \"Operational logs and support tickets often contain long natural-language text. TEXT fields can store these values without a fixed max_length setting.\"}", JsonObject.class)
);
client.insert(InsertReq.builder()
.collectionName("text_bm25_collection")
.data(rows)
.build());
// Note: DataType.Text is not supported in milvus-sdk-go as of client/v3.0.0.
// Note: DataType.Text is not supported in milvus-sdk-rust as of v3.0.2.
#include <iostream>
#include <utility>
#include "milvus/MilvusClientV2.h"
milvus::EntityRows rows = {
{{"id", static_cast<int64_t>(1)},
{"content", "Milvus stores vector embeddings and scalar fields in collections. It supports vector search, full text search, and metadata filtering for retrieval applications."}},
{{"id", static_cast<int64_t>(2)},
{"content", "Long documents are often split into passages before embedding. Store each passage in a TEXT field so search results can return the source text."}},
{{"id", static_cast<int64_t>(3)},
{"content", "Operational logs and support tickets often contain long natural-language text. TEXT fields can store these values without a fixed max_length setting."}}
};
milvus::InsertResponse response;
status = client->Insert(milvus::InsertRequest()
.WithCollectionName("text_bm25_collection")
.WithRowsData(std::move(rows)),
response);
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
}
const data = [
{
id: 1,
content: "Milvus stores vector embeddings and scalar fields in collections. It supports vector search, full text search, and metadata filtering for retrieval applications."
},
{
id: 2,
content: "Long documents are often split into passages before embedding. Store each passage in a TEXT field so search results can return the source text."
},
{
id: 3,
content: "Operational logs and support tickets often contain long natural-language text. TEXT fields can store these values without a fixed max_length setting."
}
];
const res = await client.insert({
collection_name: "text_bm25_collection",
data: data
});
console.log(res);
await client.flushSync({ collection_names: ["text_bm25_collection"] });
curl --request POST \
--url "${BASE_URL}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 30" \
-d "{
\"collectionName\": \"${COLLECTION_NAME}\",
\"data\": [
{
\"id\": 1,
\"content\": \"Milvus stores vector embeddings and scalar fields in collections. It supports vector search, full text search, and metadata filtering for retrieval applications.\"
},
{
\"id\": 2,
\"content\": \"Long documents are often split into passages before embedding. Store each passage in a TEXT field so search results can return the source text.\"
},
{
\"id\": 3,
\"content\": \"Operational logs and support tickets often contain long natural-language text. TEXT fields can store these values without a fixed max_length setting.\"
}
]
}"
# Zilliz CLI
Step 4: Perform BM25 full text search
Use raw query text as the search data and search against the sparse vector field. Milvus converts the query text into a sparse vector, ranks matches with BM25, and returns the requested TEXT field in output_fields.
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
- Zilliz CLI
results = client.search(
collection_name=COLLECTION_NAME,
data=["how does Milvus store source text for retrieval"],
anns_field="sparse",
limit=2,
output_fields=["content"],
)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.Collections;
SearchResp searchResp = client.search(SearchReq.builder()
.collectionName("text_bm25_collection")
.data(Collections.singletonList(new EmbeddedText("how does Milvus store source text for retrieval")))
.annsField("sparse")
.limit(2)
.outputFields(Collections.singletonList("content"))
.build());
// Note: DataType.Text is not supported in milvus-sdk-go as of client/v3.0.0.
// Note: DataType.Text is not supported in milvus-sdk-rust as of v3.0.2.
#include <iostream>
#include <string>
#include "milvus/MilvusClientV2.h"
std::string query_text = "how does Milvus store source text for retrieval";
auto request = milvus::SearchRequest()
.WithCollectionName("text_bm25_collection")
.WithAnnsField("sparse")
.WithLimit(2)
.AddOutputField("content")
.AddEmbeddedText(query_text);
milvus::SearchResponse response;
status = client->Search(request, response);
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
}
const res = await client.search({
collection_name: "text_bm25_collection",
data: ["how does Milvus store source text for retrieval"],
anns_field: "sparse",
limit: 2,
output_fields: ["id", "content"]
});
console.log(JSON.stringify(res.results, null, 2));
curl --request POST \
--url "${BASE_URL}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 30" \
-d "{
\"collectionName\": \"${COLLECTION_NAME}\",
\"data\": [\"how does Milvus store source text for retrieval\"],
\"annsField\": \"sparse\",
\"limit\": 2,
\"outputFields\": [\"content\"]
}"
# Zilliz CLI
Step 5: Read the returned TEXT values
Each search hit includes the BM25 score and the original TEXT value.
- Python
- Java
- Go
- Rust
- C++
- NodeJS
- cURL
- Zilliz CLI
for hit in results[0]:
print(f"id: {hit['id']}, score: {hit['distance']}")
print(hit["entity"]["content"])
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.List;
for (List<SearchResp.SearchResult> hits : searchResp.getSearchResults()) {
for (SearchResp.SearchResult hit : hits) {
System.out.println("id: " + hit.getId() + ", score: " + hit.getScore());
System.out.println(hit.getEntity().get("content"));
}
}
// Note: DataType.Text is not supported in milvus-sdk-go as of client/v3.0.0.
// Note: DataType.Text is not supported in milvus-sdk-rust as of v3.0.2.
#include <iostream>
#include "milvus/MilvusClientV2.h"
auto search_results = response.Results();
for (auto& result : search_results.Results()) {
milvus::EntityRows output_rows;
status = result.OutputRows(output_rows);
if (!status.IsOk()) {
std::cout << status.Message() << std::endl;
continue;
}
for (const auto& row : output_rows) {
std::cout << row << std::endl;
}
}
for (const hit of res.results) {
console.log(`id: ${hit.id}, score: ${hit.score}`);
console.log(hit.content);
}
curl --request POST \
--url "${BASE_URL}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"text_bm25_collection\",
\"data\": [\"how does Milvus store source text for retrieval\"],
\"annsField\": \"sparse\",
\"limit\": 2,
\"outputFields\": [\"content\"]
}"
# Zilliz CLI
For more information about BM25 functions, sparse vector indexes, and query syntax for full text search, refer to Full Text Search.