Pinecone TypeScript SDK - v9.0.0
    Preparing search index...

    Type Safety

    The Pinecone TypeScript SDK provides full type safety through generic type parameters and TypeScript interfaces. This guide shows how to leverage these features for safer, more maintainable code.

    When storing metadata alongside your vectors, you can define a TypeScript interface and pass it as a type parameter to get compile-time type checking.

    import { Pinecone, PineconeRecord } from '@pinecone-database/pinecone';

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });

    // Define your metadata structure
    type MovieMetadata = {
    title: string;
    runtime: number;
    genre: 'comedy' | 'horror' | 'drama' | 'action';
    releaseYear: number;
    director?: string; // Optional field
    };

    // Pass the type parameter when targeting the index
    const index = pc.index<MovieMetadata>({ name: 'movies' });

    // Now TypeScript will enforce the metadata structure
    await index.upsert({
    records: [
    {
    id: 'movie1',
    values: [0.1, 0.2, 0.3, 0.4],
    metadata: {
    title: 'Gone with the Wind',
    runtime: 238,
    genre: 'drama',
    releaseYear: 1939,
    },
    },
    ],
    });

    // TypeScript will catch errors
    await index.upsert({
    records: [
    {
    id: 'movie2',
    values: [0.2, 0.3, 0.4, 0.5],
    metadata: {
    title: 'The Matrix',
    runtime: 136,
    // @ts-expect-error - 'sci-fi' is not a valid genre
    genre: 'sci-fi',
    releaseYear: 1999,
    },
    },
    ],
    });

    When you use typed metadata, TypeScript ensures your filters and returned metadata are type-safe:

    import { Pinecone } from '@pinecone-database/pinecone';

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });

    type MovieMetadata = {
    title: string;
    runtime: number;
    genre: 'comedy' | 'horror' | 'drama' | 'action';
    releaseYear: number;
    };

    const index = pc.index<MovieMetadata>({ name: 'movies' });

    const results = await index.query({
    vector: [0.1, 0.2, 0.3, 0.4],
    topK: 10,
    includeMetadata: true,
    filter: {
    genre: { $eq: 'drama' },
    releaseYear: { $gte: 2000 },
    },
    });

    // TypeScript knows the metadata structure
    results.matches.forEach((match) => {
    if (match.metadata) {
    // All these properties are properly typed
    const { title, runtime, genre, releaseYear } = match.metadata;
    console.log(`${title} (${releaseYear}) - ${genre} - ${runtime} min`);

    // TypeScript will catch invalid property access
    // @ts-expect-error - 'rating' doesn't exist in MovieMetadata
    console.log(match.metadata.rating);
    }
    });

    You can define typed records for better code organization:

    import { Pinecone, PineconeRecord } from '@pinecone-database/pinecone';

    type ProductMetadata = {
    name: string;
    category: string;
    price: number;
    inStock: boolean;
    };

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });
    const index = pc.index<ProductMetadata>({ name: 'products' });

    // Define typed records
    const products: PineconeRecord<ProductMetadata>[] = [
    {
    id: 'prod1',
    values: [0.1, 0.2, 0.3],
    metadata: {
    name: 'Laptop',
    category: 'electronics',
    price: 999.99,
    inStock: true,
    },
    },
    {
    id: 'prod2',
    values: [0.2, 0.3, 0.4],
    metadata: {
    name: 'Mouse',
    category: 'electronics',
    price: 29.99,
    inStock: false,
    },
    },
    ];

    await index.upsert({ records: products });

    Use TypeScript's type system to build valid filters:

    import { Pinecone } from '@pinecone-database/pinecone';

    type ProductMetadata = {
    category: string;
    price: number;
    inStock: boolean;
    tags: string[];
    };

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });
    const index = pc.index<ProductMetadata>({ name: 'products' });

    // Type-safe filter
    const filter = {
    category: { $in: ['electronics', 'computers'] },
    price: { $gte: 100, $lte: 500 },
    inStock: { $eq: true },
    };

    const results = await index.query({
    vector: [0.1, 0.2, 0.3],
    topK: 10,
    filter,
    includeMetadata: true,
    });

    Pinecone metadata is a flat map of scalars. Every value must be a string, number, boolean, or string[] — that union is exported as RecordMetadataValue, and RecordMetadata is Record<string, RecordMetadataValue>.

    The type parameter on pc.index<T>() is constrained to RecordMetadata, so a metadata type with a nested object field is rejected at compile time rather than failing at request time. Model nested data by flattening it into individual scalar fields:

    import { Pinecone } from '@pinecone-database/pinecone';

    type ArticleMetadata = {
    title: string;
    authorName: string;
    authorEmail: string;
    tags: string[];
    publishedAt: string; // ISO date string
    views: number;
    likes: number;
    };

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });
    const index = pc.index<ArticleMetadata>({ name: 'articles' });

    await index.upsert({
    records: [
    {
    id: 'article1',
    values: [0.1, 0.2, 0.3],
    metadata: {
    title: 'Introduction to Vector Databases',
    authorName: 'Jane Doe',
    authorEmail: 'jane@example.com',
    tags: ['database', 'vector', 'ai'],
    publishedAt: '2025-01-15T10:00:00Z',
    views: 1250,
    likes: 89,
    },
    },
    ],
    });

    // Query with type-safe access
    const results = await index.query({
    vector: [0.1, 0.2, 0.3],
    topK: 5,
    includeMetadata: true,
    filter: {
    views: { $gte: 1000 },
    tags: { $in: ['ai', 'ml'] },
    },
    });

    results.matches.forEach((match) => {
    if (match.metadata) {
    console.log(match.metadata.authorName); // Properly typed!
    console.log(match.metadata.views);
    }
    });

    Use TypeScript type guards to safely handle optional metadata:

    import { Pinecone, ScoredPineconeRecord } from '@pinecone-database/pinecone';

    type MovieMetadata = {
    title: string;
    year: number;
    };

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });
    const index = pc.index<MovieMetadata>({ name: 'movies' });

    function isValidMovie(
    record: ScoredPineconeRecord<MovieMetadata>,
    ): record is ScoredPineconeRecord<MovieMetadata> & { metadata: MovieMetadata } {
    return (
    record.metadata !== undefined &&
    typeof record.metadata.title === 'string' &&
    typeof record.metadata.year === 'number'
    );
    }

    const results = await index.query({
    vector: [0.1, 0.2, 0.3],
    topK: 10,
    includeMetadata: true,
    });

    results.matches.forEach((match) => {
    if (isValidMovie(match)) {
    // TypeScript knows metadata is defined here
    console.log(`${match.metadata.title} (${match.metadata.year})`);
    }
    });

    When using integrated inference with upsertRecords, you can also define types for your records. IntegratedRecord<T> adds the id / _id field to your metadata type, so define the metadata type and let IntegratedRecord supply the identifier:

    import { Pinecone, IntegratedRecord } from '@pinecone-database/pinecone';

    type ArticleMetadata = {
    content: string; // Field to be embedded
    category: string;
    publishedAt: string;
    };

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });

    await pc.indexes.createForModel({
    name: 'articles',
    cloud: 'aws',
    region: 'us-east-1',
    embed: {
    model: 'multilingual-e5-large',
    fieldMap: { text: 'content' },
    },
    waitUntilReady: true,
    });

    const index = pc.index<ArticleMetadata>({ name: 'articles' });

    const articles: IntegratedRecord<ArticleMetadata>[] = [
    {
    id: 'art1',
    content: 'Machine learning is transforming healthcare...',
    category: 'technology',
    publishedAt: '2025-01-15T10:00:00Z',
    },
    ];

    await index.upsertRecords({ records: articles });
    1. Define metadata interfaces: Create clear interfaces for your metadata structures
    2. Use union types: For fields with limited values (like genre), use union types for better type safety
    3. Optional fields: Mark optional fields with ? to match your data model
    4. Type guards: Use type guards when metadata might be undefined
    5. Reusable types: Export metadata types for use across your application

    UpdateOptions<T> already types its metadata field as Partial<T>, so you can pass an object holding only the fields you want to change:

    import { Pinecone } from '@pinecone-database/pinecone';

    type MovieMetadata = {
    title: string;
    runtime: number;
    genre: string;
    releaseYear: number;
    };

    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });
    const index = pc.index<MovieMetadata>({ name: 'movies' });

    // Update only specific fields
    const partialUpdate: Partial<MovieMetadata> = {
    genre: 'thriller',
    };

    await index.update({
    id: 'movie1',
    metadata: partialUpdate,
    });

    Create helper functions with proper typing:

    import {
    Pinecone,
    PineconeRecord,
    QueryResponse,
    } from '@pinecone-database/pinecone';

    type ProductMetadata = {
    name: string;
    price: number;
    category: string;
    };

    async function findSimilarProducts(
    query: number[],
    category: string,
    maxPrice: number,
    ): Promise<QueryResponse<ProductMetadata>> {
    const pc = new Pinecone({ apiKey: 'YOUR_API_KEY' });
    const index = pc.index<ProductMetadata>({ name: 'products' });

    return await index.query({
    vector: query,
    topK: 10,
    filter: {
    category: { $eq: category },
    price: { $lte: maxPrice },
    },
    includeMetadata: true,
    });
    }

    // Usage
    const results = await findSimilarProducts([0.1, 0.2, 0.3], 'electronics', 500);
    results.matches.forEach((match) => {
    if (match.metadata) {
    console.log(`${match.metadata.name}: $${match.metadata.price}`);
    }
    });