#3006·typesense

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

bash
### 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-repro

Expected 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:

json
  "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