Skip to main content
A point is the fundamental unit of data in Qdrant. Each point consists of:
  • ID: A unique identifier (integer or UUID)
  • Vector: One or more dense or sparse vectors
  • Payload: Optional JSON metadata

Point Structure

Here’s what a complete point looks like:
from qdrant_client import QdrantClient, models

client = QdrantClient("localhost", port=6333)

client.upsert(
    collection_name="my_collection",
    points=[
        models.PointStruct(
            id=1,  # or uuid.uuid4()
            vector=[0.1, 0.2, 0.3, 0.4],
            payload={
                "title": "Introduction to Vector Databases",
                "author": "Jane Smith",
                "category": "technology",
                "year": 2024,
                "tags": ["vectors", "databases", "search"]
            }
        )
    ]
)
Every point must have an ID and at least one vector. Payload is optional but highly recommended for filtering and retrieval.

Point IDs

Qdrant supports two types of point IDs:

Integer IDs (u64)

Simple numeric identifiers from 0 to 2^64-1:
models.PointStruct(
    id=42,
    vector=[0.1, 0.2, 0.3],
    payload={"name": "example"}
)

UUID IDs

Universally unique identifiers for distributed systems:
import uuid

models.PointStruct(
    id=uuid.UUID("550e8400-e29b-41d4-a716-446655440000"),
    vector=[0.1, 0.2, 0.3],
    payload={"name": "example"}
)
// From lib/segment/src/types.rs:162-169
pub enum ExtendedPointId {
    NumId(u64),
    Uuid(Uuid),
}
Use UUID IDs when:
  • Integrating with external systems that use UUIDs
  • Building distributed systems where IDs are generated independently
  • You need guaranteed global uniqueness
Point IDs within a collection must be unique. Upserting a point with an existing ID will replace the old point entirely.

Vector Data

Single Vector

Most common case - one vector per point:
models.PointStruct(
    id=1,
    vector=[0.1, 0.2, 0.3, 0.4],  # Must match collection's vector size
    payload={"text": "example"}
)

Named Vectors

Multiple vectors of different types per point:
models.PointStruct(
    id=1,
    vector={
        "image": [0.1, 0.2, ...],  # 512-dim image embedding
        "text": [0.3, 0.4, ...]     # 384-dim text embedding
    },
    payload={"title": "Multimodal example"}
)

Sparse Vectors

For high-dimensional sparse data:
models.PointStruct(
    id=1,
    vector={
        "dense": [0.1, 0.2, 0.3],
        "sparse": models.SparseVector(
            indices=[10, 25, 103, 507],
            values=[0.5, 0.3, 0.8, 0.2]
        )
    },
    payload={"text": "hybrid search example"}
)

Payloads

Payloads are JSON objects attached to points for filtering and retrieval:
payload = {
    # String values
    "title": "Vector Database Guide",
    "category": "technology",
    
    # Numeric values
    "price": 29.99,
    "rating": 4.5,
    "year": 2024,
    
    # Boolean values
    "published": True,
    "featured": False,
    
    # Arrays
    "tags": ["vectors", "database", "search"],
    "colors": ["red", "blue", "green"],
    
    # Nested objects
    "author": {
        "name": "Jane Smith",
        "email": "jane@example.com"
    },
    
    # Geo points
    "location": {
        "lon": -0.1276,
        "lat": 51.5074
    },
    
    # Datetime (RFC3339 format)
    "created_at": "2024-01-15T10:30:00Z"
}
See Payloads for detailed information on payload types and indexing.

Batch Operations

Upserting Multiple Points

Efficiently insert or update many points at once:
points = [
    models.PointStruct(
        id=i,
        vector=[0.1 * i, 0.2 * i, 0.3 * i],
        payload={"index": i, "category": f"cat_{i % 5}"}
    )
    for i in range(1000)
]

client.upsert(
    collection_name="my_collection",
    points=points
)
Batch operations are much more efficient than individual upserts. Aim for batches of 100-1000 points.

Retrieving Points by ID

# Get specific points
points = client.retrieve(
    collection_name="my_collection",
    ids=[1, 2, 3, 42],
    with_payload=True,
    with_vectors=True
)

for point in points:
    print(f"ID: {point.id}")
    print(f"Vector: {point.vector}")
    print(f"Payload: {point.payload}")

Deleting Points

Delete by IDs:
client.delete(
    collection_name="my_collection",
    points_selector=models.PointIdsList(
        points=[1, 2, 3]
    )
)
Delete by filter:
client.delete(
    collection_name="my_collection",
    points_selector=models.FilterSelector(
        filter=models.Filter(
            must=[
                models.FieldCondition(
                    key="year",
                    range=models.Range(lt=2020)
                )
            ]
        )
    )
)

Scrolling Through Points

Iterate through all points in a collection:
offset = None
all_points = []

while True:
    result = client.scroll(
        collection_name="my_collection",
        limit=100,
        offset=offset,
        with_payload=True,
        with_vectors=False
    )
    
    points, next_offset = result
    all_points.extend(points)
    
    if next_offset is None:
        break
    
    offset = next_offset

print(f"Total points: {len(all_points)}")
// From lib/collection/src/operations/types.rs:624-632
pub struct ScrollResult {
    /// List of retrieved points
    pub points: Vec<api::rest::Record>,
    /// Offset for next page
    pub next_page_offset: Option<PointIdType>,
}
Use scrolling for exports, backups, or full collection processing. For search, use the search endpoint instead.

Point Versioning

Each point has an internal version number that increments with each update:
// From lib/segment/src/types.rs:372-388
pub struct ScoredPoint {
    pub id: PointIdType,
    pub version: SeqNumberType,  // Sequential version number
    pub score: ScoreType,
    pub payload: Option<Payload>,
    pub vector: Option<VectorStructInternal>,
    pub shard_key: Option<ShardKey>,
    pub order_value: Option<OrderValue>,
}
Versions are used internally for consistency and can help with debugging updates.

Update Strategies

Upsert (Overwrite)

Replaces the entire point:
client.upsert(
    collection_name="my_collection",
    points=[
        models.PointStruct(
            id=1,
            vector=[0.9, 0.8, 0.7],
            payload={"new": "data"}  # Replaces old payload completely
        )
    ]
)

Set Payload (Merge)

Merges with existing payload:
client.set_payload(
    collection_name="my_collection",
    payload={"category": "updated", "new_field": "value"},
    points=[1, 2, 3]
)

Overwrite Payload (Replace)

Replaces payload without changing vector:
client.overwrite_payload(
    collection_name="my_collection",
    payload={"only": "this"},
    points=[1, 2, 3]
)

Delete Payload Keys

Remove specific fields:
client.delete_payload(
    collection_name="my_collection",
    keys=["old_field", "temporary_data"],
    points=[1, 2, 3]
)

Best Practices

Always prefer batch upserts over individual operations. Batching 100-1000 points provides optimal performance.
Stick with integer IDs unless you specifically need UUIDs. Integer IDs are more compact and efficient.
If using Cosine distance, normalize your vectors before insertion for consistent results.
Create payload indexes for fields you frequently filter on to improve query performance.
Keep payload sizes reasonable (less than 1KB per point). Store large objects externally and reference them in the payload.

Collections

Learn how collections organize and configure points

Vectors

Deep dive into vector types and configurations

Payloads

Understand payload structure and indexing

Indexing

Explore how points are indexed for fast search