{
  "schema_version": 2,
  "id": "develop/clients/php/vecsearch",
  "title": "Index and query vectors",
  "url": "https://redis.io/docs/latest/develop/clients/php/vecsearch/",
  "summary": "Learn how to index and query vector embeddings with Redis",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-10-16T16:44:37+01:00",
  "page_type": "content",
  "content_hash": "7051f6ee0e3b0aeb327fb8744a9cab66722e5205ea5ce84d8a27cf04e09e07d1",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "[Redis Search](https://redis.io/docs/latest/develop/ai/search-and-query)\nlets you index vector fields in [hash](https://redis.io/docs/latest/develop/data-types/hashes)\nor [JSON](https://redis.io/docs/latest/develop/data-types/json) objects (see the\n[Vectors](https://redis.io/docs/latest/develop/ai/search-and-query/vectors) \nreference page for more information).\nAmong other things, vector fields can store *text embeddings*, which are AI-generated vector\nrepresentations of the semantic information in pieces of text. The\n[vector distance](https://redis.io/docs/latest/develop/ai/search-and-query/vectors#distance-metrics)\nbetween two embeddings indicates how similar they are semantically. By comparing the\nsimilarity of an embedding generated from some query text with embeddings stored in hash\nor JSON fields, Redis can retrieve documents that closely match the query in terms\nof their meaning.\n\nThe example below uses the [HuggingFace](https://huggingface.co/) model\n[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)\nto generate the vector embeddings to store and index with Redis Search.\nThe code is first demonstrated for hash documents with a\nseparate section to explain the\n[differences with JSON documents](#differences-with-json-documents).\n\n> [!NOTE]\n> From [v3.0.0](https://github.com/predis/predis/releases/tag/v3.0.0) onwards,\n> `Predis` uses query dialect 2 by default.\n> Redis Search methods such as [`ftSearch()`](https://redis.io/docs/latest/commands/ft.search)\n> will explicitly request this dialect, overriding the default set for the server.\n> See\n> [Query dialects](https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/dialects)\n> for more information."
    },
    {
      "id": "initialize",
      "title": "Initialize",
      "role": "content",
      "text": "You can use the [TransformersPHP](https://transformers.codewithkyrian.com/)\nlibrary to create the vector embeddings. Install the library with the following\ncommand:\n\n[code example]"
    },
    {
      "id": "import-dependencies",
      "title": "Import dependencies",
      "role": "content",
      "text": "Import the following classes and function in your source file:\n\n[code example]"
    },
    {
      "id": "create-a-tokenizer-instance",
      "title": "Create a tokenizer instance",
      "role": "content",
      "text": "The code below shows how to use the\n[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)\ntokenizer to generate the embeddings. The vectors that represent the\nembeddings have 384 dimensions, regardless of the length of the input\ntext. Here, the `pipeline()` call creates the `$extractor` function that\ngenerates embeddings from text.\n\nThe `all-MiniLM-L6-v2` model expects mean pooling followed by normalization\nto unit length (see the\n[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)\npage for more information). In TransformersPHP v0.6.2, the `normalize: true`\noption has no effect. The `$embed()` function below calls `$extractor()` with\nmean pooling and then normalizes the vector itself:\n\n[code example]"
    },
    {
      "id": "create-the-index",
      "title": "Create the index",
      "role": "content",
      "text": "Connect to Redis and delete any index previously created with the\nname `vector_idx`. (The\n[`ftdropindex()`](https://redis.io/docs/latest/commands/ft.dropindex)\ncall throws an exception if the index doesn't already exist, which is\nwhy you need the `try...catch` block.)\n\n[code example]\n\nNext, create the index.\nThe schema in the example below includes three fields: the text content to index, a\n[tag](https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/tags)\nfield to represent the \"genre\" of the text, and the embedding vector generated from\nthe original text content. The `embedding` field specifies\n[HNSW](https://redis.io/docs/latest/develop/ai/search-and-query/vectors#hnsw-index) \nindexing, the\n[L2](https://redis.io/docs/latest/develop/ai/search-and-query/vectors#distance-metrics)\nvector distance metric, `Float32` values to represent the vector's components,\nand 384 dimensions, as required by the `all-MiniLM-L6-v2` embedding model.\n\nThe `CreateArguments` parameter to [`ftcreate()`](https://redis.io/docs/latest/commands/ft.create)\nspecifies hash objects for storage and a prefix `doc:` that identifies the hash objects\nto index.\n\n[code example]"
    },
    {
      "id": "add-data",
      "title": "Add data",
      "role": "content",
      "text": "You can now supply the data objects, which will be indexed automatically\nwhen you add them with [`hmset()`](https://redis.io/docs/latest/commands/hset), as long as\nyou use the `doc:` prefix specified in the index definition.\n\nUse the `$embed()` function as shown below to create the embedding that\nrepresents the `content` field.\n\nTo add an embedding as a field of a hash object, you must encode the\nvector array as a binary string. The built-in\n[`pack()`](https://www.php.net/manual/en/function.pack.php) function is a convenient\nway to do this in PHP, using the `g*` format specifier to denote a packed\narray of `float` values. Note that if you are using\n[JSON](https://redis.io/docs/latest/develop/data-types/json)\nobjects to store your documents instead of hashes, then you should store\nthe `float` array directly without first converting it to a binary\nstring (see [Differences with JSON documents](#differences-with-json-documents)\nbelow).\n\n[code example]"
    },
    {
      "id": "bulk-import-with-himport",
      "title": "Bulk import with HIMPORT",
      "role": "content",
      "text": "From Redis Open Source 8.10, you can also add the documents with the\n[`HIMPORT`](https://redis.io/docs/latest/commands/himport) command family. This creates the same\nhashes as `hmset()`, but lets Redis store them as\n[compact hashes](https://redis.io/docs/latest/develop/data-types/hashes#compact-hashes), which use\nless memory when many keys share the same field names. Redis Search indexes\nthese hashes in the same way.\n\nDeclare the field names once by calling `himport()` with\n[`PREPARE`](https://redis.io/docs/latest/commands/himport-prepare), then create each\ndocument by calling it with [`SET`](https://redis.io/docs/latest/commands/himport-set), passing only\nits values, in the same order as the field names. Use a\n[pipeline](https://redis.io/docs/latest/develop/clients/php/transpipe) to send all the\nimports in one round trip:\n\n[code example]\n\n`HIMPORT SET` replaces any existing key, so running this code after the\n`hmset()` calls leaves you with the same three documents.\n\n> [!NOTE]\n> `HIMPORT` requires `Predis` v3.6.0 or later, and `Predis` marks its\n> support as experimental, so the API might change. Redis Cloud and\n> Redis Software don't support `HIMPORT`."
    },
    {
      "id": "run-a-query",
      "title": "Run a query",
      "role": "content",
      "text": "After you have created the index and added the data, you are ready to run a query.\nTo do this, you must create another embedding vector from your chosen query\ntext. Redis calculates the vector distance between the query vector and each\nembedding vector in the index as it runs the query. You can request the results to be\nsorted to rank them in order of ascending distance.\n\nThe code below creates the query embedding using the `$embed()` function, as with\nthe indexing, and passes it as a parameter when the query executes (see\n[Vector search](https://redis.io/docs/latest/develop/ai/search-and-query/query/vector-search)\nfor more information about using query parameters with embeddings).\nThe query is a\n[K nearest neighbors (KNN)](https://redis.io/docs/latest/develop/ai/search-and-query/vectors#knn-vector-search)\nsearch that sorts the results in order of vector distance from the query vector.\n\nThe results are returned as an array with the number of results in the\nfirst element. The remaining elements are alternating pairs with the\nkey of the returned document (for example, `doc:0`) first, followed by an array containing\nthe fields you requested (again as alternating key-value pairs).\n\n[code example]\n\nAssuming you have added the code from the steps above to your source file,\nit is now ready to run, but note that it may take a while to complete when\nyou run it for the first time (which happens because the tokenizer must download the\n`all-MiniLM-L6-v2` model data before it can\ngenerate the embeddings). When you run the code, it outputs the following result text:\n\n[code example]\n\nThe exact distances may differ slightly on your system.\n\nNote that the results are ordered according to the value of the `vector_distance`\nfield, with the lowest distance indicating the greatest similarity to the query.\nAs you would expect, the text *\"That is a very happy person\"* (from the `doc:0`\ndocument)\nis the result judged to be most similar in meaning to the query text\n*\"That is a happy person\"*."
    },
    {
      "id": "differences-with-json-documents",
      "title": "Differences with JSON documents",
      "role": "content",
      "text": "Indexing JSON documents is similar to hash indexing, but there are some\nimportant differences. JSON allows much richer data modeling with nested fields, so\nyou must supply a [path](https://redis.io/docs/latest/develop/data-types/json/path) in the schema\nto identify each field you want to index. However, you can declare a short alias for each\nof these paths to avoid typing it in full for\nevery query. Also, you must specify `JSON` with the `on()` option when you create the index.\n\nThe code below shows these differences, but the index is otherwise very similar to\nthe one created previously for hashes:\n\n[code example]\n\nUse [`jsonset()`](https://redis.io/docs/latest/commands/json.set) to add the data\ninstead of [`hmset()`](https://redis.io/docs/latest/commands/hset). The arrays\nthat specify the fields have roughly the same structure as the ones used for\n`hmset()` but you should use the standard library function\n[`json_encode()`](https://www.php.net/manual/en/function.json-encode.php)\nto generate a JSON string representation of the array.\n\nAn important difference with JSON indexing is that the vectors are\nspecified using arrays instead of binary strings. Add the\nembedding as an array field without using the `pack()` function as you\nwould with a hash.\n\n[code example]\n\nThe query is almost identical to the one for the hash documents. This\ndemonstrates how the right choice of aliases for the JSON paths can\nsave you having to write complex queries. An important thing to notice\nis that the vector parameter for the query is still specified as a\nbinary string (using the `pack()` function), even though the data for\nthe `embedding` field of the JSON was specified as an array.\n\n[code example]\n\nApart from the `jdoc:` prefixes for the keys, the result from the JSON\nquery is the same as for hash:\n\n[code example]"
    },
    {
      "id": "learn-more",
      "title": "Learn more",
      "role": "related",
      "text": "See\n[Vector search](https://redis.io/docs/latest/develop/ai/search-and-query/query/vector-search)\nfor more information about the indexing options, distance metrics, and query format\nfor vectors."
    }
  ],
  "examples": [
    {
      "id": "initialize-ex0",
      "language": "bash",
      "code": "composer require codewithkyrian/transformers",
      "section_id": "initialize"
    },
    {
      "id": "import-dependencies-ex0",
      "language": "php",
      "code": "<?php\n\nrequire 'vendor/autoload.php';\n\n// TransformersPHP\nuse function Codewithkyrian\\Transformers\\Pipelines\\pipeline;\n\n// Redis client and Redis Search classes.\nuse Predis\\Client;\nuse Predis\\Command\\Argument\\Search\\CreateArguments;\nuse Predis\\Command\\Argument\\Search\\SearchArguments;\nuse Predis\\Command\\Argument\\Search\\SchemaFields\\TextField;\nuse Predis\\Command\\Argument\\Search\\SchemaFields\\TagField;\nuse Predis\\Command\\Argument\\Search\\SchemaFields\\VectorField;",
      "section_id": "import-dependencies"
    },
    {
      "id": "create-a-tokenizer-instance-ex0",
      "language": "php",
      "code": "$extractor = pipeline('embeddings', 'Xenova/all-MiniLM-L6-v2');\n\n$embed = function (string $text) use ($extractor): array {\n    $emb = $extractor($text, pooling: 'mean')[0];\n    $norm = sqrt(array_sum(array_map(fn($x) => $x * $x, $emb)));\n\n    return array_map(fn($x) => $x / $norm, $emb);\n};",
      "section_id": "create-a-tokenizer-instance"
    },
    {
      "id": "create-the-index-ex0",
      "language": "php",
      "code": "$client = new Predis\\Client([\n    'host' => 'localhost',\n    'port' => 6379,\n]);\n\ntry {\n    $client->ftdropindex(\"vector_idx\");\n} catch (Exception $e){}",
      "section_id": "create-the-index"
    },
    {
      "id": "create-the-index-ex1",
      "language": "php",
      "code": "$schema = [\n    new TextField(\"content\"),\n    new TagField(\"genre\"),\n    new VectorField(\n        \"embedding\",\n        \"HNSW\",\n        [\n            \"TYPE\", \"FLOAT32\",\n            \"DIM\", 384,\n            \"DISTANCE_METRIC\", \"L2\"\n        ]\n    )   \n];\n\n$client->ftcreate(\"vector_idx\", $schema,\n    (new CreateArguments())\n        ->on('HASH')\n        ->prefix([\"doc:\"])\n);",
      "section_id": "create-the-index"
    },
    {
      "id": "add-data-ex0",
      "language": "php",
      "code": "$content = \"That is a very happy person\";\n$emb = $embed($content);\n\n$client->hmset(\"doc:0\",[\n    \"content\" => $content,\n    \"genre\" => \"persons\",\n    \"embedding\" => pack('g*', ...$emb)\n]);\n\n$content = \"That is a happy dog\";\n$emb = $embed($content);\n\n$client->hmset(\"doc:1\",[\n    \"content\" => $content,\n    \"genre\" => \"pets\",\n    \"embedding\" => pack('g*', ...$emb)\n]);\n\n$content = \"Today is a sunny day\";\n$emb = $embed($content);\n\n$client->hmset(\"doc:2\",[\n    \"content\" => $content,\n    \"genre\" => \"weather\",\n    \"embedding\" => pack('g*', ...$emb)\n]);",
      "section_id": "add-data"
    },
    {
      "id": "bulk-import-with-himport-ex0",
      "language": "php",
      "code": "$docs = [\n    [\"doc:0\", \"That is a very happy person\", \"persons\"],\n    [\"doc:1\", \"That is a happy dog\", \"pets\"],\n    [\"doc:2\", \"Today is a sunny day\", \"weather\"],\n];\n\n$client->pipeline(function ($pipe) use ($docs, $embed) {\n    $pipe->himport('PREPARE', \"doc\", [\"content\", \"genre\", \"embedding\"]);\n\n    foreach ($docs as [$key, $content, $genre]) {\n        $emb = $embed($content);\n\n        $pipe->himport('SET', $key, \"doc\", [\n            $content,\n            $genre,\n            pack('g*', ...$emb)\n        ]);\n    }\n});",
      "section_id": "bulk-import-with-himport"
    },
    {
      "id": "run-a-query-ex0",
      "language": "php",
      "code": "$queryText = \"That is a happy person\";\n$queryEmb = $embed($queryText);\n\n$result = $client->ftsearch(\n    \"vector_idx\",\n    '*=>[KNN 3 @embedding $vec AS vector_distance]',\n    (new SearchArguments())\n        ->addReturn(1, \"vector_distance\")\n        ->dialect(\"2\")\n        ->params([\n            \"vec\", pack('g*', ...$queryEmb)\n        ])\n        ->sortBy(\"vector_distance\")\n);\n\n$numResults = $result[0];\necho \"Number of results: $numResults\" . PHP_EOL;\n// >>> Number of results: 3\n\nfor ($i = 1; $i < ($numResults * 2 + 1); $i += 2) {\n    $key = $result[$i];\n    echo \"Key: $key\" . PHP_EOL;\n    $fields = $result[$i + 1];\n    echo \"Field: {$fields[0]}, Value: {$fields[1]}\" . PHP_EOL; \n}        \n// >>> Key: doc:0\n// >>> Field: vector_distance, Value: 0.133717402816\n// >>> Key: doc:1\n// >>> Field: vector_distance, Value: 0.621603608131\n// >>> Key: doc:2\n// >>> Field: vector_distance, Value: 1.46157670021",
      "section_id": "run-a-query"
    },
    {
      "id": "run-a-query-ex1",
      "language": "plaintext",
      "code": "Number of results: 3\nKey: doc:0\nField: vector_distance, Value: 0.133717402816\nKey: doc:1\nField: vector_distance, Value: 0.621603608131\nKey: doc:2\nField: vector_distance, Value: 1.46157670021",
      "section_id": "run-a-query"
    },
    {
      "id": "differences-with-json-documents-ex0",
      "language": "php",
      "code": "$jsonSchema = [\n    new TextField(\"$.content\", \"content\"),\n    new TagField(\"$.genre\", \"genre\"),\n    new VectorField(\n        \"$.embedding\",\n        \"HNSW\",\n        [\n            \"TYPE\", \"FLOAT32\",\n            \"DIM\", 384,\n            \"DISTANCE_METRIC\", \"L2\"\n        ],\n        \"embedding\",\n    )   \n];\n\n$client->ftcreate(\"vector_json_idx\", $jsonSchema,\n    (new CreateArguments())\n        ->on('JSON')\n        ->prefix([\"jdoc:\"])\n);",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex1",
      "language": "php",
      "code": "$content = \"That is a very happy person\";\n$emb = $embed($content);\n\n$client->jsonset(\"jdoc:0\", \"$\",\n    json_encode(\n        [\n            \"content\" => $content,\n            \"genre\" => \"persons\",\n            \"embedding\" => $emb\n        ],\n        JSON_THROW_ON_ERROR\n    )\n);\n\n$content = \"That is a happy dog\";\n$emb = $embed($content);\n\n$client->jsonset(\"jdoc:1\",\"$\", \n    json_encode(\n        [\n            \"content\" => $content,\n            \"genre\" => \"pets\",\n            \"embedding\" => $emb\n        ],\n        JSON_THROW_ON_ERROR\n    )\n);\n\n$content = \"Today is a sunny day\";\n$emb = $embed($content);\n\n$client->jsonset(\"jdoc:2\", \"$\",\n    json_encode(\n        [\n            \"content\" => $content,\n            \"genre\" => \"weather\",\n            \"embedding\" => $emb\n        ],\n        JSON_THROW_ON_ERROR\n    )\n);",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex2",
      "language": "php",
      "code": "$queryText = \"That is a happy person\";\n$queryEmb = $embed($queryText);\n\n$result = $client->ftsearch(\n    \"vector_json_idx\",\n    '*=>[KNN 3 @embedding $vec AS vector_distance]',\n    (new SearchArguments())\n        ->addReturn(1, \"vector_distance\")\n        ->dialect(\"2\")\n        ->params([\n            \"vec\", pack('g*', ...$queryEmb)\n        ])\n        ->sortBy(\"vector_distance\")\n);",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex3",
      "language": "plaintext",
      "code": "Number of results: 3\nKey: jdoc:0\nField: vector_distance, Value: 0.133717402816\nKey: jdoc:1\nField: vector_distance, Value: 0.621603608131\nKey: jdoc:2\nField: vector_distance, Value: 1.46157670021",
      "section_id": "differences-with-json-documents"
    }
  ]
}
