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 |