Joined fields with multiple pinned hits returns same pinned hit for all
Author: cmigazziCreated Jul 29, 2026Updated Sep 11, 2026
Labelsbug
Bug Description
With a joined field and multiple pinned hits in the results, the same joined data is attached to all pinned hits.
Linked to #2587
Reproduction Steps
### Run Typesense via Docker ########################################
set -x
export TYPESENSE_API_KEY=xyz
export TYPESENSE_HOST=http://localhost:8108
docker stop typesense-repro 2>/dev/null
docker rm typesense-repro 2>/dev/null
rm -rf "$(pwd)"/typesense-data-dir-repro
mkdir "$(pwd)"/typesense-data-dir-repro
# Wait for Typesense to be ready
docker run -d -p 8108:8108 --name typesense-repro \
-v"$(pwd)"/typesense-data-dir-repro:/data \
typesense/typesense:30.2 \
--data-dir /data \
--api-key=$TYPESENSE_API_KEY \
--enable-cors
# Wait till typesense is ready.
until curl -s -o /dev/null -w "%{http_code}" "$TYPESENSE_HOST/health" -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | grep -q "200"; do
sleep 2
done
curl -s "$TYPESENSE_HOST/debug" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | jq
curl -s "$TYPESENSE_HOST/collections" \
-X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '
{
"name": "products",
"fields": [
{"name": "name", "type": "string" }
]
}' | jq
curl -s "$TYPESENSE_HOST/collections/products/documents/import?action=create" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-H "Content-Type: text/plain" \
-X POST \
-d '{"id": "124","name": "First Product"}
{"id": "125","name": "Second Product"}
{"id": "126","name": "Third Product"}' | jq
curl -s "$TYPESENSE_HOST/collections" \
-X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '
{
"name": "product_data",
"fields": [
{"name": "product_id", "type": "string", "reference": "products.id" },
{"name": "extra_data", "type": "string" }
]
}' | jq
curl -s "$TYPESENSE_HOST/collections/product_data/documents/import?action=create" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-H "Content-Type: text/plain" \
-X POST \
-d '{"id": "11","product_id": "124","extra_data": "foo"}
{"id": "15","product_id": "125","extra_data": "hello"}
{"id": "34","product_id": "126","extra_data": "bar"}' | jq
curl -s "$TYPESENSE_HOST/collections/products/documents/search" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-X GET \
-G \
--data-urlencode "q=*" \
--data-urlencode 'filter_by=$product_data(id:*)' \
--data-urlencode "pinned_hits=124:1,125:2" \
--data-urlencode "filter_curated_hits=true" | jq '.'
docker stop typesense-repro
docker rm typesense-reproExpected vs Actual
Expected behavior Each pinned hit should have its own related joined data.
Actual behavior The id: "124" contains the product_data of product_id:"125:
"hits": [
{
"curated": true,
"document": {
"id": "124",
"name": "First Product",
"product_data": {
"extra_data": "hello",
"id": "15",
"product_id": "125"
}
},
"highlight": {},
"highlights": []
},
{
"curated": true,
"document": {
"id": "125",
"name": "Second Product",
"product_data": {
"extra_data": "hello",
"id": "15",
"product_id": "125"
}
},
"highlight": {},
"highlights": []
},Environment
- Typesense version:
v30.2
Source: typesense/typesense