From ac380bd2212dc9d5f6f228adb970349c194aecff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nguy=E1=BB=85n=20Nam=20Long?=
<26674308+J2TeamNNL@users.noreply.github.com>
Date: Sun, 1 Nov 2026 08:55:25 +0700
Subject: [PATCH 1/9] docs(databases): add the Qdrant driver page
---
connections/connection-form.mdx | 1 +
connections/ssl.mdx | 2 +-
databases/index.mdx | 5 +-
databases/qdrant.mdx | 112 ++++++++++++++++++++++++++++++++
docs.json | 1 +
index.mdx | 2 +-
snippets/driver-counts.mdx | 4 +-
7 files changed, 121 insertions(+), 6 deletions(-)
create mode 100644 databases/qdrant.mdx
diff --git a/connections/connection-form.mdx b/connections/connection-form.mdx
index 686c1c4..23b9eec 100644
--- a/connections/connection-form.mdx
+++ b/connections/connection-form.mdx
@@ -198,6 +198,7 @@ Metadata connections are the extra ones opened to read a database's object list
| [Elasticsearch](/databases/elasticsearch) | 9200 | Yes | Yes | Yes | No | Yes | Yes |
| [Typesense](/databases/typesense) | 8108 | Yes | Yes | Yes | No | Yes | Yes |
| [Weaviate](/databases/weaviate) | 8080 | Yes | Yes | Yes | No | Yes | Yes |
+| [Qdrant](/databases/qdrant) | 6333 | Yes | Yes | Yes | No | Yes | Yes |
| [SAP HANA](/databases/sap-hana) | 443 | No | Yes | No | No | No | No |
| [Snowflake](/databases/snowflake) | 443 | No | No | No | No | No | No |
| [SQLite](/databases/sqlite) | File | No | No | No | No | No | No |
diff --git a/connections/ssl.mdx b/connections/ssl.mdx
index 35de11a..281cd39 100644
--- a/connections/ssl.mdx
+++ b/connections/ssl.mdx
@@ -43,7 +43,7 @@ A new connection starts on the mode that matches the driver's own default, and s
| MySQL, MariaDB | Preferred | TLS when the server offers it, plain when it does not, and a plain retry after an SSL handshake error. Auth and network errors are not retried |
| SQL Server | Preferred | FreeTDS `encryption=request`: the login over TLS, the rest in plain TCP unless the server forces encryption |
| Teradata | Disabled | Opens a TLS transport, retries on a plain socket if it fails to come up |
-| MongoDB, Redis, Cassandra, ClickHouse, Elasticsearch, Typesense, SurrealDB, Weaviate | Disabled | Nothing. No fallback exists, so Preferred forces TLS exactly like Required |
+| MongoDB, Redis, Cassandra, ClickHouse, Elasticsearch, Typesense, SurrealDB, Weaviate, Qdrant | Disabled | Nothing. No fallback exists, so Preferred forces TLS exactly like Required |
| etcd | Disabled | Nothing. The driver never reads these fields. Set **TLS Mode** on the Options section instead, and see [etcd](/databases/etcd) |
| Trino, Presto | Disabled | Sends every request over HTTPS with no fallback, again like Required |
| SAP HANA | Verify Identity | Nothing. No fallback exists, so Preferred forces TLS exactly like Required |
diff --git a/databases/index.mdx b/databases/index.mdx
index 6916e3f..7e646f1 100644
--- a/databases/index.mdx
+++ b/databases/index.mdx
@@ -1,11 +1,11 @@
---
title: Supported Databases
-description: All 42 engines TablePro connects to, their default ports, and which ones need a plugin
+description: All 43 engines TablePro connects to, their default ports, and which ones need a plugin
---
import DriverCounts from "/snippets/driver-counts.mdx";
-Forty-two engines, and every one of them is free to use. What differs between them is where the
+Forty-three engines, and every one of them is free to use. What differs between them is where the
driver comes from, not what the license covers.
@@ -43,6 +43,7 @@ driver comes from, not what the license covers.
| [PGlite](/databases/pglite) | 5432 | Built-in |
| [PostgreSQL](/databases/postgresql) | 5432 | Built-in |
| [Presto](/databases/trino) | 8080 | Plugin |
+| [Qdrant](/databases/qdrant) | 6333 | Plugin |
| [Redis](/databases/redis) | 6379 | Built-in |
| [SAP HANA](/databases/sap-hana) | 443 | Plugin |
| [ScyllaDB](/databases/cassandra) | 9042 | Plugin |
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
new file mode 100644
index 0000000..f095034
--- /dev/null
+++ b/databases/qdrant.mdx
@@ -0,0 +1,112 @@
+---
+title: Qdrant
+description: Connect to Qdrant, browse collections as points, edit payloads by id, and run REST requests
+---
+
+import RegistryPlugin from "/snippets/registry-plugin.mdx";
+
+Collections are tables, points are rows, and the point `id` is the primary key. The editor takes Qdrant REST requests, not SQL, so a vector search such as `POST /collections/docs/points/query` runs from the editor and its points and scores land in the grid.
+
+
+
+## Quick setup
+
+Click **New Connection…**, select **Qdrant**, enter host and port, paste an **API Key** if the node needs one, then click **Save & Connect**.
+
+There is no Database field. One connection reaches one Qdrant instance, and its collections are the objects.
+
+For a node on a private server, open the **Network** tab and set **Connect via** to **SSH Tunnel**, then set **Host** to the address Qdrant listens on as the SSH server sees it, usually `localhost`. See [SSH Tunneling](/connections/ssh-tunneling).
+
+## Connection settings
+
+| Field | Description |
+|-------|-------------|
+| **Host** | Node hostname. `localhost` for Docker, the cluster URL's host for Qdrant Cloud |
+| **Port** | `6333` by default, the REST port. Qdrant Cloud also answers on `6333` |
+| **API Key** | Sent as the `api-key` header. Leave it empty for a node started without `QDRANT__SERVICE__API_KEY` |
+| **Skip TLS Verification** | Options tab. Trusts any certificate, even under **Verify CA** or **Verify Identity** |
+
+Qdrant Cloud requires the API key and HTTPS. Set **SSL Mode** to **Required**, or **Verify Identity** if you have the CA.
+
+## Browsing collections
+
+The sidebar lists collections from `GET /collections`.
+
+Every grid has `id` first and the vectors last. `id` is the primary key, and both are read-only. A collection with one unnamed vector shows a `vector` column. A collection with named or sparse vectors shows one column per name, such as `vector.image`. A vector is shown as a JSON number array, and a sparse vector as its `indices` and `values`.
+
+Payload columns are the indexed payload fields, then the fields found in the first 100 points. Arrays and objects render as JSON in the cell. A field that appears only in later points shows up in a query result but not as a grid column.
+
+Unsorted pages load through `POST /points/scroll`, which pages by point id. Paging forward reuses the id the last page ended on. A jump to a far page first reads point ids alone up to that row. A sort becomes an `order_by` on `POST /points/query`, which needs a range payload index on that field. Qdrant orders by one field, so only the first sorted column counts. Sorting by `id` keeps the default order.
+
+## Filtering
+
+Filter values are typed from the payload index, or from the values in the sampled points when the field has no index.
+
+| Operators | What Qdrant runs |
+|----------|------------------|
+| equals, not equals | `match.value` for text, integers and booleans. A float or a date matches as a closed `range` |
+| greater than, greater or equal, less than, less or equal, between | `range`, numbers and dates only |
+| in list, not in list | `match.any`, `match.except` over a comma-separated list |
+| contains, not contains | `match.text`, which needs a full-text payload index on the field |
+| is NULL, is not NULL | `is_null` |
+| is empty, is not empty | `is_empty` |
+
+On `id`, equals and in list become `has_id`. **Starts with**, **ends with** and **REGEX** have no Qdrant equivalent, and a filter Qdrant cannot run reports why instead of returning the whole collection. The raw filter row takes one Qdrant condition as JSON, such as `{"key": "city", "match": {"value": "Berlin"}}`.
+
+Filtered row counts come from `POST /points/count` with `exact` set.
+
+## Editing points
+
+Grid edits are REST requests keyed by `id`, shown in the review sheet before they run.
+
+| Change | Request |
+|--------|---------|
+| New row | `PUT /collections/{name}/points` with `update_mode` set to `insert_only`, so an id that already exists is left alone. An empty `id` gets a new UUID. The point is stored without vectors |
+| Edited cells | `POST /collections/{name}/points/payload`, which sets only the edited keys |
+| Deleted row | `POST /collections/{name}/points/delete` |
+
+A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
+
+## REST editor
+
+Type a request line, then a JSON body on the same line or on the lines under it:
+
+```http
+POST /collections/docs/points/query
+{"query": [0.1, 0.2, 0.3, 0.4], "limit": 10, "with_payload": true}
+```
+
+A response that lists points, from `query`, `scroll`, `search` or a retrieve by id, renders as a grid with `id`, then `score` when there is one, the payload fields, and any vectors returned. Anything else is shown as formatted JSON.
+
+Read-only mode allows `GET` and the read endpoints under `/points`: `query`, `scroll`, `search`, `count`, `recommend`, `discover`, `facet`, their batch and group forms, and a retrieve by id. A `delete`, a `clear` and `points/batch` count as destructive.
+
+## SSL/TLS
+
+New connections start on **Disabled**, plain HTTP. Every other mode goes over HTTPS. **Preferred** and **Required (skip verify)** accept a self-signed certificate. See [SSL/TLS](/connections/ssl).
+
+## Limitations
+
+- SQL is not available. Use the REST editor.
+- gRPC is not used.
+- Payload indexes and collection settings are not edited from Structure. Send the REST request, such as `PUT /collections/{name}/index`.
+- A new row has no vectors. Upsert vectors through the REST editor.
+- The plugin is registry-only. It is not on iPhone or iPad.
+
+## Troubleshooting
+
+### Must provide an API key
+
+The node was started with an API key and **API Key** is empty. Paste the key and connect again.
+
+### Invalid API key or JWT
+
+The key was rejected. The health check reads `/collections`, which needs the key, so a revoked key also drops a live session.
+
+### No range index for `order_by` key
+
+The sorted column has no range payload index. Create one with `PUT /collections/{name}/index`, or clear the sort.
+
+## Related
+
+- [Import & Export](/features/import-export)
+- [Filtering](/features/filtering)
diff --git a/docs.json b/docs.json
index 7d896d6..22079a4 100644
--- a/docs.json
+++ b/docs.json
@@ -265,6 +265,7 @@
"databases/oracle",
"databases/pglite",
"databases/postgresql",
+ "databases/qdrant",
"databases/redis",
"databases/sap-hana",
"databases/snowflake",
diff --git a/index.mdx b/index.mdx
index c64e11f..7079bb9 100644
--- a/index.mdx
+++ b/index.mdx
@@ -1,6 +1,6 @@
---
title: Introduction
-description: Native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis, and 37 more
+description: Native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis, and 38 more
---
import DriverCounts from "/snippets/driver-counts.mdx";
diff --git a/snippets/driver-counts.mdx b/snippets/driver-counts.mdx
index 44a8722..2f0288b 100644
--- a/snippets/driver-counts.mdx
+++ b/snippets/driver-counts.mdx
@@ -1,3 +1,3 @@
-Five drivers ship inside the app and cover twelve databases. Twenty-six registry plugins cover the
-other thirty and install on the first connection that needs one. See
+Five drivers ship inside the app and cover twelve databases. Twenty-seven registry plugins cover the
+other thirty-one and install on the first connection that needs one. See
[Plugins](/features/plugins).
From 9cc2c37a492ac34c7aaf40ce0117194c805cb2bc Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 20:38:54 +0700
Subject: [PATCH 2/9] docs(databases): match the Qdrant page to the reworked
driver
---
connections/ssl.mdx | 2 +-
databases/index.mdx | 4 +--
databases/qdrant.mdx | 59 ++++++++++++++++++++++++++------------
index.mdx | 2 +-
snippets/driver-counts.mdx | 4 +--
5 files changed, 46 insertions(+), 25 deletions(-)
diff --git a/connections/ssl.mdx b/connections/ssl.mdx
index 9b3dfcd..a73811d 100644
--- a/connections/ssl.mdx
+++ b/connections/ssl.mdx
@@ -26,7 +26,7 @@ The certificate rows appear only once **SSL Mode** is anything but Disabled. Eac
| Field | When it appears | What it takes |
|---|---|---|
-| **Certificate**, under **CA Certificate** | Verify CA and Verify Identity only | The PEM or DER certificate the server's certificate chains to, for example `/path/to/ca-cert.pem`. Saving without it is blocked, except under Verify Identity on ClickHouse, Kafka, Presto, SAP HANA and Trino, which check the system trust store when it is empty |
+| **Certificate**, under **CA Certificate** | Verify CA and Verify Identity only | The PEM or DER certificate the server's certificate chains to, for example `/path/to/ca-cert.pem`. Saving without it is blocked, except under Verify Identity on ClickHouse, Kafka, Presto, Qdrant, SAP HANA and Trino, which check the system trust store when it is empty |
| **Client Certificate** | Any mode but Disabled | Your certificate, for a server that enforces mutual TLS. Optional otherwise |
| **Client Key** | Any mode but Disabled | The matching private key. Required once a client certificate is set |
| **Key Passphrase** | Once **Client Key** has a path, on drivers that support it (Cassandra, ScyllaDB and MongoDB) | The passphrase of an encrypted key. Stored in the Keychain |
diff --git a/databases/index.mdx b/databases/index.mdx
index aae0ff5..59d68d0 100644
--- a/databases/index.mdx
+++ b/databases/index.mdx
@@ -1,11 +1,11 @@
---
title: Supported Databases
-description: All 43 engines TablePro connects to, their default ports, and which ones need a plugin
+description: All 46 engines TablePro connects to, their default ports, and which ones need a plugin
---
import DriverCounts from "/snippets/driver-counts.mdx";
-Forty-three engines, and every one of them is free to use. What differs between them is where the
+Forty-six engines, and every one of them is free to use. What differs between them is where the
driver comes from, not what the license covers.
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index f095034..1fe9a9b 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -1,6 +1,6 @@
---
title: Qdrant
-description: Connect to Qdrant, browse collections as points, edit payloads by id, and run REST requests
+description: Connect to Qdrant, browse collections as points, edit payloads and vectors by id, and run REST requests
---
import RegistryPlugin from "/snippets/registry-plugin.mdx";
@@ -24,19 +24,27 @@ For a node on a private server, open the **Network** tab and set **Connect via**
| **Host** | Node hostname. `localhost` for Docker, the cluster URL's host for Qdrant Cloud |
| **Port** | `6333` by default, the REST port. Qdrant Cloud also answers on `6333` |
| **API Key** | Sent as the `api-key` header. Leave it empty for a node started without `QDRANT__SERVICE__API_KEY` |
-| **Skip TLS Verification** | Options tab. Trusts any certificate, even under **Verify CA** or **Verify Identity** |
-Qdrant Cloud requires the API key and HTTPS. Set **SSL Mode** to **Required**, or **Verify Identity** if you have the CA.
+Qdrant Cloud requires the API key and HTTPS. Set **SSL Mode** to **Verify Identity** with no CA certificate: the certificate is checked against the system trust store.
## Browsing collections
The sidebar lists collections from `GET /collections`.
-Every grid has `id` first and the vectors last. `id` is the primary key, and both are read-only. A collection with one unnamed vector shows a `vector` column. A collection with named or sparse vectors shows one column per name, such as `vector.image`. A vector is shown as a JSON number array, and a sparse vector as its `indices` and `values`.
+Every grid has `id` first and the vectors last. `id` is the primary key and read-only. A collection with one unnamed vector shows a `vector` column. A collection with named or sparse vectors shows one column per name, such as `vector.image`. A dense vector is a JSON number array, a multivector an array of them, and a sparse vector its `indices` and `values`.
-Payload columns are the indexed payload fields, then the fields found in the first 100 points. Arrays and objects render as JSON in the cell. A field that appears only in later points shows up in a query result but not as a grid column.
+Payload columns are the top-level indexed fields, then the fields found in the first 100 points. Arrays and objects render as JSON in the cell. An index on a nested path such as `metadata.source` or `tags[]` is listed in the **Indexes** section of the **Structure** tab, and its value shows inside the `metadata` or `tags` column. A field that appears only in later points shows up in a query result but not as a grid column.
-Unsorted pages load through `POST /points/scroll`, which pages by point id. Paging forward reuses the id the last page ended on. A jump to a far page first reads point ids alone up to that row. A sort becomes an `order_by` on `POST /points/query`, which needs a range payload index on that field. Qdrant orders by one field, so only the first sorted column counts. Sorting by `id` keeps the default order.
+Unsorted pages load through `POST /points/scroll`, which pages by point id. Paging forward reuses the id the last page ended on, and a jump to a far page reads point ids alone up to that row.
+
+## Sorting
+
+Sort by one payload field that has an `integer`, `float` or `datetime` payload index. Points with a value in that field come first, in order. Points without one follow, by id. Every point stays reachable, and the row count still matches the collection.
+
+Sorting by `id` ascending is the default order. These sorts are refused with a message:
+
+- `id` descending, a vector, or more than one column.
+- A field that holds a list in any point, since Qdrant would list that point once per value.
## Filtering
@@ -46,12 +54,12 @@ Filter values are typed from the payload index, or from the values in the sample
|----------|------------------|
| equals, not equals | `match.value` for text, integers and booleans. A float or a date matches as a closed `range` |
| greater than, greater or equal, less than, less or equal, between | `range`, numbers and dates only |
-| in list, not in list | `match.any`, `match.except` over a comma-separated list |
-| contains, not contains | `match.text`, which needs a full-text payload index on the field |
-| is NULL, is not NULL | `is_null` |
-| is empty, is not empty | `is_empty` |
+| in list, not in list | Equals for each value: `match.any` for text or integers, one `range` per float or date |
+| contains, not contains | `match.text` on a text field, with or without a full-text index. It matches whole words and ignores case, so `world` finds `Hello World` but `lo Wo` finds nothing |
+| is NULL, is not NULL | `is_empty`: the key is missing, `null`, or an empty list |
+| is empty, is not empty | `is_empty`, or the empty string on a text field |
-On `id`, equals and in list become `has_id`. **Starts with**, **ends with** and **REGEX** have no Qdrant equivalent, and a filter Qdrant cannot run reports why instead of returning the whole collection. The raw filter row takes one Qdrant condition as JSON, such as `{"key": "city", "match": {"value": "Berlin"}}`.
+Not equals, not in list and not contains also match points the key is missing from. On `id`, equals and in list become `has_id`. **Starts with**, **ends with** and **REGEX** have no Qdrant equivalent, and a filter Qdrant cannot run reports why instead of returning the whole collection. The raw filter row takes one Qdrant condition as JSON, such as `{"key": "city", "match": {"value": "Berlin"}}`.
Filtered row counts come from `POST /points/count` with `exact` set.
@@ -61,15 +69,16 @@ Grid edits are REST requests keyed by `id`, shown in the review sheet before the
| Change | Request |
|--------|---------|
-| New row | `PUT /collections/{name}/points` with `update_mode` set to `insert_only`, so an id that already exists is left alone. An empty `id` gets a new UUID. The point is stored without vectors |
-| Edited cells | `POST /collections/{name}/points/payload`, which sets only the edited keys |
+| New row | `POST /collections/{name}/points` first, to check that no point has the id, then `PUT /collections/{name}/points` with `update_mode` set to `insert_only`. An empty `id` gets a new UUID. Vector cells you fill are stored with the point |
+| Edited payload cells | `POST /collections/{name}/points/payload`, which sets only the edited keys |
+| Edited vector cells | `PUT /collections/{name}/points/vectors`. Setting a vector to NULL sends `POST /collections/{name}/points/vectors/delete` |
| Deleted row | `POST /collections/{name}/points/delete` |
-A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
+A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`, and a `bool` field takes `true`, `false`, `1` or `0`. A value that does not fit, such as `4.5` in an `integer` column or a vector that is not a number array, stops the save and names the column. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
## REST editor
-Type a request line, then a JSON body on the same line or on the lines under it:
+Type a request line, then a JSON body on the same line, on the lines under it, or starting on the line and going on under it:
```http
POST /collections/docs/points/query
@@ -78,18 +87,26 @@ POST /collections/docs/points/query
A response that lists points, from `query`, `scroll`, `search` or a retrieve by id, renders as a grid with `id`, then `score` when there is one, the payload fields, and any vectors returned. Anything else is shown as formatted JSON.
-Read-only mode allows `GET` and the read endpoints under `/points`: `query`, `scroll`, `search`, `count`, `recommend`, `discover`, `facet`, their batch and group forms, and a retrieve by id. A `delete`, a `clear` and `points/batch` count as destructive.
+Read-only mode allows `GET`, `POST /collections/{name}/facet`, and the read endpoints under `/points`: `query`, `scroll`, `search`, `count`, `recommend`, `discover`, their batch and group forms, and a retrieve by id. A `DELETE`, a path ending in `delete` or `clear`, and `points/batch` count as destructive. A snapshot request is flagged as reaching the server's disk.
## SSL/TLS
-New connections start on **Disabled**, plain HTTP. Every other mode goes over HTTPS. **Preferred** and **Required (skip verify)** accept a self-signed certificate. See [SSL/TLS](/connections/ssl).
+New connections start on **Disabled**, plain HTTP. Every other mode goes over HTTPS.
+
+| SSL Mode | What is checked |
+|----------|-----------------|
+| Preferred, Required (skip verify) | Nothing. A self-signed certificate is accepted |
+| Verify CA | The certificate chains to the CA certificate you choose. The hostname is not checked |
+| Verify Identity | The certificate chains to the CA certificate, or to the system trust store when none is chosen, and names the host |
+
+For a node that enforces mutual TLS, choose a **Client Certificate** and its **Client Key**. See [SSL/TLS](/connections/ssl).
## Limitations
- SQL is not available. Use the REST editor.
- gRPC is not used.
- Payload indexes and collection settings are not edited from Structure. Send the REST request, such as `PUT /collections/{name}/index`.
-- A new row has no vectors. Upsert vectors through the REST editor.
+- A field that holds a list in any point cannot be sorted.
- The plugin is registry-only. It is not on iPhone or iPad.
## Troubleshooting
@@ -104,7 +121,11 @@ The key was rejected. The health check reads `/collections`, which needs the key
### No range index for `order_by` key
-The sorted column has no range payload index. Create one with `PUT /collections/{name}/index`, or clear the sort.
+The sorted column has no `integer`, `float` or `datetime` payload index. Create one with `PUT /collections/{name}/index`, or clear the sort.
+
+### A row with this key already exists
+
+A new row's `id` is already taken. Clear the `id` cell to get a new UUID, or edit the existing point instead.
## Related
diff --git a/index.mdx b/index.mdx
index 7079bb9..f5fc5e7 100644
--- a/index.mdx
+++ b/index.mdx
@@ -1,6 +1,6 @@
---
title: Introduction
-description: Native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis, and 38 more
+description: Native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis, and 41 more
---
import DriverCounts from "/snippets/driver-counts.mdx";
diff --git a/snippets/driver-counts.mdx b/snippets/driver-counts.mdx
index 2f0288b..6200a2a 100644
--- a/snippets/driver-counts.mdx
+++ b/snippets/driver-counts.mdx
@@ -1,3 +1,3 @@
-Five drivers ship inside the app and cover twelve databases. Twenty-seven registry plugins cover the
-other thirty-one and install on the first connection that needs one. See
+Five drivers ship inside the app and cover twelve databases. Thirty registry plugins cover the
+other thirty-four and install on the first connection that needs one. See
[Plugins](/features/plugins).
From 071407fe6f9df1094c0bcba19d51d2a1122ac5a0 Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 21:25:43 +0700
Subject: [PATCH 3/9] docs(databases): note Qdrant 1.15 for grid inserts and
the Verify CA purpose check
---
databases/qdrant.mdx | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index 1fe9a9b..e4f3df4 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -69,12 +69,12 @@ Grid edits are REST requests keyed by `id`, shown in the review sheet before the
| Change | Request |
|--------|---------|
-| New row | `POST /collections/{name}/points` first, to check that no point has the id, then `PUT /collections/{name}/points` with `update_mode` set to `insert_only`. An empty `id` gets a new UUID. Vector cells you fill are stored with the point |
+| New row | `POST /collections/{name}/points` first, to check that no point has the id, then `PUT /collections/{name}/points` with an `update_filter` that matches no point, so an id taken in between is left alone. An empty `id` gets a new UUID. Vector cells you fill are stored with the point. Needs Qdrant 1.15 or later, since older nodes ignore the filter and overwrite the point |
| Edited payload cells | `POST /collections/{name}/points/payload`, which sets only the edited keys |
| Edited vector cells | `PUT /collections/{name}/points/vectors`. Setting a vector to NULL sends `POST /collections/{name}/points/vectors/delete` |
| Deleted row | `POST /collections/{name}/points/delete` |
-A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`, and a `bool` field takes `true`, `false`, `1` or `0`. A value that does not fit, such as `4.5` in an `integer` column or a vector that is not a number array, stops the save and names the column. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
+A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`, and a `bool` field takes `true`, `false`, `1` or `0`. An indexed field that holds lists has the type `json(keyword)` or similar: it takes a JSON list such as `["a", "b"]`, or one value of the index's type. A value that does not fit, such as `4.5` in an `integer` column or a vector that is not a number array, stops the save and names the column. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
## REST editor
@@ -96,7 +96,7 @@ New connections start on **Disabled**, plain HTTP. Every other mode goes over HT
| SSL Mode | What is checked |
|----------|-----------------|
| Preferred, Required (skip verify) | Nothing. A self-signed certificate is accepted |
-| Verify CA | The certificate chains to the CA certificate you choose. The hostname is not checked |
+| Verify CA | The certificate chains to the CA certificate you choose and is issued for a server. The hostname is not checked |
| Verify Identity | The certificate chains to the CA certificate, or to the system trust store when none is chosen, and names the host |
For a node that enforces mutual TLS, choose a **Client Certificate** and its **Client Key**. See [SSL/TLS](/connections/ssl).
@@ -107,6 +107,7 @@ For a node that enforces mutual TLS, choose a **Client Certificate** and its **C
- gRPC is not used.
- Payload indexes and collection settings are not edited from Structure. Send the REST request, such as `PUT /collections/{name}/index`.
- A field that holds a list in any point cannot be sorted.
+- Adding rows from the grid needs Qdrant 1.15 or later. On an older node, upsert from the REST editor.
- The plugin is registry-only. It is not on iPhone or iPad.
## Troubleshooting
From 9a7d98537a8199056e51218c192c1775453dbc55 Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 21:59:42 +0700
Subject: [PATCH 4/9] docs(databases): explain how Qdrant filters a field
without an index
---
databases/qdrant.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index e4f3df4..25b673d 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -48,7 +48,7 @@ Sorting by `id` ascending is the default order. These sorts are refused with a m
## Filtering
-Filter values are typed from the payload index, or from the values in the sampled points when the field has no index.
+A field with a payload index is filtered as its index type. A field without one may hold different types in different points, so a value matches every type it can be: `42` finds the text `"42"`, the number 42, and a list holding either.
| Operators | What Qdrant runs |
|----------|------------------|
From b0347ad8770f6320600ba83b047e464e7c4a44d3 Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 22:17:29 +0700
Subject: [PATCH 5/9] docs(databases): note that contains on a Qdrant id finds
a whole id
---
databases/qdrant.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index 25b673d..002efb5 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -59,7 +59,7 @@ A field with a payload index is filtered as its index type. A field without one
| is NULL, is not NULL | `is_empty`: the key is missing, `null`, or an empty list |
| is empty, is not empty | `is_empty`, or the empty string on a text field |
-Not equals, not in list and not contains also match points the key is missing from. On `id`, equals and in list become `has_id`. **Starts with**, **ends with** and **REGEX** have no Qdrant equivalent, and a filter Qdrant cannot run reports why instead of returning the whole collection. The raw filter row takes one Qdrant condition as JSON, such as `{"key": "city", "match": {"value": "Berlin"}}`.
+Not equals, not in list and not contains also match points the key is missing from. On `id`, equals and in list become `has_id`, and contains finds a whole id only. **Starts with**, **ends with** and **REGEX** have no Qdrant equivalent, and a filter Qdrant cannot run reports why instead of returning the whole collection. The raw filter row takes one Qdrant condition as JSON, such as `{"key": "city", "match": {"value": "Berlin"}}`.
Filtered row counts come from `POST /points/count` with `exact` set.
From 5e186b326a8eeb5eb64a2905b444cae2f4533536 Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 22:28:38 +0700
Subject: [PATCH 6/9] docs(databases): describe Qdrant json columns
---
databases/qdrant.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index 002efb5..f975b38 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -74,7 +74,7 @@ Grid edits are REST requests keyed by `id`, shown in the review sheet before the
| Edited vector cells | `PUT /collections/{name}/points/vectors`. Setting a vector to NULL sends `POST /collections/{name}/points/vectors/delete` |
| Deleted row | `POST /collections/{name}/points/delete` |
-A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`, and a `bool` field takes `true`, `false`, `1` or `0`. An indexed field that holds lists has the type `json(keyword)` or similar: it takes a JSON list such as `["a", "b"]`, or one value of the index's type. A value that does not fit, such as `4.5` in an `integer` column or a vector that is not a number array, stops the save and names the column. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
+A cell is written back as the type its column holds: a `keyword` field stays a string even when it reads `42`, and a `bool` field takes `true`, `false`, `1` or `0`. A column whose points hold different types, such as text in one and a number in another, has the type `json`: its text shows in quotes, and a cell saves as the JSON it spells, so `"42"` stays text and `42` a number. A value that does not fit, such as `4.5` in an `integer` column or a vector that is not a number array, stops the save and names the column. **Truncate** deletes every point with an empty filter and keeps the collection. **Drop** removes the collection.
## REST editor
From 19275b54b51379763ec5cc550b7a78046ed7b9c2 Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 22:38:03 +0700
Subject: [PATCH 7/9] docs(databases): contains on a Qdrant number or vector
finds nothing
---
databases/qdrant.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index f975b38..c05b829 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -55,7 +55,7 @@ A field with a payload index is filtered as its index type. A field without one
| equals, not equals | `match.value` for text, integers and booleans. A float or a date matches as a closed `range` |
| greater than, greater or equal, less than, less or equal, between | `range`, numbers and dates only |
| in list, not in list | Equals for each value: `match.any` for text or integers, one `range` per float or date |
-| contains, not contains | `match.text` on a text field, with or without a full-text index. It matches whole words and ignores case, so `world` finds `Hello World` but `lo Wo` finds nothing |
+| contains, not contains | `match.text`, with or without a full-text index. It matches whole words and ignores case, so `world` finds `Hello World` but `lo Wo` finds nothing. A number, a boolean or a vector matches nothing |
| is NULL, is not NULL | `is_empty`: the key is missing, `null`, or an empty list |
| is empty, is not empty | `is_empty`, or the empty string on a text field |
From 2f0e3edf54ba91511d32fd8949276242042763c1 Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 23:08:13 +0700
Subject: [PATCH 8/9] docs(databases): Qdrant pages load through the Query API
---
databases/qdrant.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index c05b829..0679ff8 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -35,7 +35,7 @@ Every grid has `id` first and the vectors last. `id` is the primary key and read
Payload columns are the top-level indexed fields, then the fields found in the first 100 points. Arrays and objects render as JSON in the cell. An index on a nested path such as `metadata.source` or `tags[]` is listed in the **Indexes** section of the **Structure** tab, and its value shows inside the `metadata` or `tags` column. A field that appears only in later points shows up in a query result but not as a grid column.
-Unsorted pages load through `POST /points/scroll`, which pages by point id. Paging forward reuses the id the last page ended on, and a jump to a far page reads point ids alone up to that row.
+Pages load through `POST /points/query` in id order, with the offset applied by Qdrant, so a page always shows the rows at that position now. A node older than 1.10 has no Query API, so its pages walk the point ids from the start.
## Sorting
From 84e84da571e5367974f2c280b5f846be17de581d Mon Sep 17 00:00:00 2001
From: Ngo Quoc Dat
Date: Sun, 1 Nov 2026 23:34:15 +0700
Subject: [PATCH 9/9] docs(databases): Qdrant filters match every type a value
spells
---
databases/qdrant.mdx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/databases/qdrant.mdx b/databases/qdrant.mdx
index 0679ff8..a13bc8d 100644
--- a/databases/qdrant.mdx
+++ b/databases/qdrant.mdx
@@ -48,13 +48,13 @@ Sorting by `id` ascending is the default order. These sorts are refused with a m
## Filtering
-A field with a payload index is filtered as its index type. A field without one may hold different types in different points, so a value matches every type it can be: `42` finds the text `"42"`, the number 42, and a list holding either.
+A payload index does not limit what a field holds, so a value matches every type it can be: `42` finds the text `"42"`, the number 42, and a list holding either. The index still speeds up the part of the filter of its own type.
| Operators | What Qdrant runs |
|----------|------------------|
-| equals, not equals | `match.value` for text, integers and booleans. A float or a date matches as a closed `range` |
-| greater than, greater or equal, less than, less or equal, between | `range`, numbers and dates only |
-| in list, not in list | Equals for each value: `match.any` for text or integers, one `range` per float or date |
+| equals, not equals | `match.value` for the text, plus a closed `range` when it is a number and a boolean match for `true`, `false`, `1` or `0` |
+| greater than, greater or equal, less than, less or equal, between | `range`, on numbers, or on dates for any other text |
+| in list, not in list | Equals for each value, collapsed to `match.any` when every value is plain text |
| contains, not contains | `match.text`, with or without a full-text index. It matches whole words and ignores case, so `world` finds `Hello World` but `lo Wo` finds nothing. A number, a boolean or a vector matches nothing |
| is NULL, is not NULL | `is_empty`: the key is missing, `null`, or an empty list |
| is empty, is not empty | `is_empty`, or the empty string on a text field |