> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hyperspell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Metadata

> Add custom metadata to your memories and filter search results

## Overview

Custom metadata allows you to attach arbitrary key-value pairs to your memories, enabling powerful filtering capabilities when querying. This is useful for categorizing documents by department, priority level, confidentiality, or any other custom attributes relevant to your application.

## Adding Metadata

### Text Memories

When adding text memories via `/memories/add`, include a `metadata` object with your custom fields:

<CodeGroup>
  ```python Python theme={null}
  from hyperspell import Hyperspell

  client = Hyperspell(api_key="API_KEY", user_id="YOUR_USER_ID")

  memory = client.memories.add(
      text="Q1 planning meeting notes discussing product roadmap.",
      resource_id="meeting-2024-q1",
      metadata={
          "department": "engineering",
          "priority": 5,
          "confidential": True,
          "meeting_date": "2024-01-15T09:00:00Z"
      }
  )
  print(memory.resource_id)
  ```

  ```typescript TypeScript theme={null}
  import Hyperspell from 'hyperspell';

  const client = new Hyperspell({ apiKey: 'API_KEY', userID: 'YOUR_USER_ID' });

  const memory = await client.memories.add({
      text: 'Q1 planning meeting notes discussing product roadmap.',
      resourceId: 'meeting-2024-q1',
      metadata: {
          department: 'engineering',
          priority: 5,
          confidential: true,
          meeting_date: '2024-01-15T09:00:00Z'
      }
  });
  console.log(memory.resource_id);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.hyperspell.com/memories/add" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "text": "Q1 planning meeting notes discussing product roadmap.",
      "resource_id": "meeting-2024-q1",
      "metadata": {
        "department": "engineering",
        "priority": 5,
        "confidential": true,
        "meeting_date": "2024-01-15T09:00:00Z"
      }
    }'
  ```
</CodeGroup>

### Supported Value Types

Metadata values can be:

* **Strings**: `"department": "engineering"`
* **Numbers**: `"priority": 5` or `"score": 0.95`
* **Booleans**: `"confidential": true`
* **ISO 8601 Dates**: `"meeting_date": "2024-01-15T09:00:00Z"`

### File Uploads

When uploading files via `/memories/upload`, pass metadata as a JSON string in the form data:

<CodeGroup>
  ```python Python theme={null}
  import json
  from hyperspell import Hyperspell

  client = Hyperspell(api_key="API_KEY", user_id="YOUR_USER_ID")

  with open("quarterly_report.pdf", "rb") as file:
      memory = client.memories.upload(
          file=file,
          metadata=json.dumps({
              "collection": "reports",
              "department": "finance",
              "quarter": "Q1",
              "year": 2024,
              "confidential": True
          })
      )
  print(memory.resource_id)
  ```

  ```typescript TypeScript theme={null}
  import Hyperspell from 'hyperspell';
  import * as fs from 'fs';

  const client = new Hyperspell({ apiKey: 'API_KEY', userID: 'YOUR_USER_ID' });

  const fileBuffer = fs.readFileSync('quarterly_report.pdf');
  const file = new File([fileBuffer], 'quarterly_report.pdf', { type: 'application/pdf' });

  const memory = await client.memories.upload({
      file: file,
      metadata: JSON.stringify({
          collection: 'reports',
          department: 'finance',
          quarter: 'Q1',
          year: 2024,
          confidential: true
      })
  });
  console.log(memory.resource_id);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.hyperspell.com/memories/upload" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "file=@quarterly_report.pdf" \
    -F 'metadata={"collection": "reports", "department": "finance", "quarter": "Q1", "year": 2024, "confidential": true}'
  ```
</CodeGroup>

<Note>
  For file uploads, metadata must be passed as a JSON-encoded string since the endpoint uses multipart form data.
</Note>

### Updating Metadata

When you add a memory with the same `resource_id`, the metadata is merged with any existing metadata. New keys are added, and existing keys are overwritten:

<CodeGroup>
  ```python Python theme={null}
  # First call creates the resource
  client.memories.add(
      text="Initial notes",
      resource_id="doc-123",
      metadata={
          "department": "engineering",
          "priority": 3
      }
  )

  # Second call merges metadata
  client.memories.add(
      text="Updated notes",
      resource_id="doc-123",
      metadata={
          "priority": 5,
          "reviewed": True
      }
  )

  # Result: metadata = { "department": "engineering", "priority": 5, "reviewed": True }
  ```

  ```typescript TypeScript theme={null}
  // First call creates the resource
  await client.memories.add({
      text: 'Initial notes',
      resourceId: 'doc-123',
      metadata: {
          department: 'engineering',
          priority: 3
      }
  });

  // Second call merges metadata
  await client.memories.add({
      text: 'Updated notes',
      resourceId: 'doc-123',
      metadata: {
          priority: 5,
          reviewed: true
      }
  });

  // Result: metadata = { department: "engineering", priority: 5, reviewed: true }
  ```
</CodeGroup>

### Using the Update Endpoint

For more granular updates, use the `/memories/update` endpoint. This endpoint allows you to update metadata, title, or text without re-indexing if text is not provided. It works with documents from any source (vault, slack, gmail, etc.):

<CodeGroup>
  ```python Python theme={null}
  from hyperspell import Hyperspell

  client = Hyperspell(api_key="API_KEY", user_id="YOUR_USER_ID")

  # Update only metadata without re-indexing
  client.memories.update(
      source="vault",
      resource_id="doc-123",
      metadata={
          "priority": 5,
          "reviewed": True
      }
  )

  # Update multiple fields at once
  client.memories.update(
      source="vault",
      resource_id="doc-123",
      title="New Title",
      metadata={
          "collection": "new-collection",
          "status": "published"
      }
  )
  ```

  ```typescript TypeScript theme={null}
  import Hyperspell from 'hyperspell';

  const client = new Hyperspell({ apiKey: 'API_KEY', userID: 'YOUR_USER_ID' });

  // Update only metadata without re-indexing
  await client.memories.update({
      source: 'vault',
      resourceId: 'doc-123',
      metadata: {
          priority: 5,
          reviewed: true
      }
  });

  // Update multiple fields at once
  await client.memories.update({
      source: 'vault',
      resourceId: 'doc-123',
      title: 'New Title',
      metadata: {
          collection: 'new-collection',
          status: 'published'
      }
  });
  ```

  ```bash cURL theme={null}
  # Update only metadata
  curl -X POST "https://api.hyperspell.com/memories/update/vault/doc-123" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "metadata": {
        "priority": 5,
        "reviewed": true
      }
    }'

  # Update multiple fields
  curl -X POST "https://api.hyperspell.com/memories/update/vault/doc-123" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "title": "New Title",
      "metadata": {
        "collection": "new-collection",
        "status": "published"
      }
    }'

  ```
</CodeGroup>

<Note>
  The update endpoint only modifies fields you explicitly provide. Fields you don't include remain unchanged.
</Note>

## Querying with Metadata Filters

Use the `options.filter` parameter when querying to filter results by metadata. Filters use MongoDB-style operators and are combined with AND logic.

### Basic Filtering

Filter by exact value match:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="product roadmap",
      sources=["vault"],
      options={
          "filter": {
              "department": "engineering"
          }
      }
  )
  print(response.documents)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'product roadmap',
      sources: ['vault'],
      options: {
          filter: {
              department: 'engineering'
          }
      }
  });
  console.log(response.documents);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.hyperspell.com/memories/query" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "product roadmap",
      "options": {
        "filter": {
          "department": "engineering"
        }
      }
    }'
  ```
</CodeGroup>

### Multiple Conditions

Multiple conditions are combined with AND logic:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="meeting notes",
      sources=["vault"],
      options={
          "filter": {
              "department": "engineering",
              "confidential": False
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'meeting notes',
      sources: ['vault'],
      options: {
          filter: {
              department: 'engineering',
              confidential: false
          }
      }
  });
  ```
</CodeGroup>

### Comparison Operators

Use MongoDB-style operators for advanced filtering:

| Operator | Description           | Example                                                 |
| -------- | --------------------- | ------------------------------------------------------- |
| `$eq`    | Equal to (implicit)   | `{"priority": 5}` or `{"priority": {"$eq": 5}}`         |
| `$ne`    | Not equal to          | `{"department": {"$ne": "sales"}}`                      |
| `$gt`    | Greater than          | `{"priority": {"$gt": 3}}`                              |
| `$gte`   | Greater than or equal | `{"priority": {"$gte": 3}}`                             |
| `$lt`    | Less than             | `{"priority": {"$lt": 5}}`                              |
| `$lte`   | Less than or equal    | `{"priority": {"$lte": 5}}`                             |
| `$in`    | Value in list         | `{"department": {"$in": ["engineering", "marketing"]}}` |

### Complex Filter Examples

**High priority, non-confidential documents:**

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="important updates",
      sources=["vault"],
      options={
          "filter": {
              "priority": {"$gte": 4},
              "confidential": False
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'important updates',
      sources: ['vault'],
      options: {
          filter: {
              priority: { $gte: 4 },
              confidential: false
          }
      }
  });
  ```
</CodeGroup>

**Documents from specific departments:**

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="team updates",
      sources=["vault"],
      options={
          "filter": {
              "department": {"$in": ["engineering", "product", "design"]}
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'team updates',
      sources: ['vault'],
      options: {
          filter: {
              department: { $in: ['engineering', 'product', 'design'] }
          }
      }
  });
  ```
</CodeGroup>

**Exclude certain categories:**

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="company news",
      sources=["vault"],
      options={
          "filter": {
              "category": {"$ne": "internal"}
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'company news',
      sources: ['vault'],
      options: {
          filter: {
              category: { $ne: 'internal' }
          }
      }
  });
  ```
</CodeGroup>

**Range queries on numeric values:**

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="documents",
      sources=["vault"],
      options={
          "filter": {
              "priority": {"$gt": 2, "$lte": 8}
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'documents',
      sources: ['vault'],
      options: {
          filter: {
              priority: { $gt: 2, $lte: 8 }
          }
      }
  });
  ```
</CodeGroup>

### Combining with Resource ID Filters

Metadata filters can be combined with `resource_ids` to search within specific documents:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="quarterly results",
      sources=["vault"],
      options={
          "resource_ids": ["report-q1", "report-q2"],
          "filter": {
              "department": "finance"
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'quarterly results',
      sources: ['vault'],
      options: {
          resource_ids: ['report-q1', 'report-q2'],
          filter: {
              department: 'finance'
          }
      }
  });
  ```
</CodeGroup>

### Combining with Date Filters

Metadata filters can be combined with the `after` and `before` date range options:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="quarterly reports",
      sources=["vault"],
      options={
          "after": "2024-01-01",
          "before": "2024-12-31",
          "filter": {
              "department": "finance",
              "confidential": False
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'quarterly reports',
      sources: ['vault'],
      options: {
          after: '2024-01-01',
          before: '2024-12-31',
          filter: {
              department: 'finance',
              confidential: false
          }
      }
  });
  ```
</CodeGroup>

## Filtering the List Endpoint

You can also use metadata filters when listing all memories via `/memories/list`. Pass the filter as a URL-encoded JSON string:

<CodeGroup>
  ```python Python theme={null}
  import json
  from hyperspell import Hyperspell

  client = Hyperspell(api_key="API_KEY", user_id="YOUR_USER_ID")

  # List only engineering documents
  response = client.memories.list(
      filter=json.dumps({"department": "engineering"})
  )
  print(f"Found {len(response.items)} documents")
  ```

  ```typescript TypeScript theme={null}
  import Hyperspell from 'hyperspell';

  const client = new Hyperspell({ apiKey: 'API_KEY', userID: 'YOUR_USER_ID' });

  // List high-priority documents
  const response = await client.memories.list({
      filter: JSON.stringify({ priority: { $gte: 5 } })
  });
  console.log(`Found ${response.items.length} documents`);
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.hyperspell.com/memories/list?filter=%7B%22department%22%3A%22engineering%22%7D" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

All the same filter operators work with the list endpoint: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, and `$in`.

## Metadata in Query Results

When you query documents, the custom metadata is included in each document's `metadata` field alongside system fields:

```json theme={null}
{
  "documents": [
    {
      "resource_id": "meeting-2024-q1",
      "source": "vault",
      "title": "Q1 Planning Meeting",
      "metadata": {
        "status": "completed",
        "indexed_at": "2024-01-15T10:30:00Z",
        "department": "engineering",
        "priority": 5,
        "confidential": true,
        "meeting_date": "2024-01-15T09:00:00Z"
      },
      "highlights": [...]
    }
  ]
}
```

You can access this metadata in your code:

<CodeGroup>
  ```python Python theme={null}
  response = client.memories.search(
      query="engineering roadmap",
      sources=["vault"],
      options={
          "filter": {"department": "engineering"}
      }
  )

  for doc in response.documents:
      print(f"Title: {doc.title}")
      print(f"Department: {doc.metadata.get('department')}")
      print(f"Priority: {doc.metadata.get('priority')}")
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'engineering roadmap',
      sources: ['vault'],
      options: {
          filter: { department: 'engineering' }
      }
  });

  for (const doc of response.documents) {
      console.log(`Title: ${doc.title}`);
      console.log(`Department: ${doc.metadata?.department}`);
      console.log(`Priority: ${doc.metadata?.priority}`);
  }
  ```
</CodeGroup>

## Best Practices

1. **Use consistent key names** across your application to enable reliable filtering
2. **Keep metadata flat** - nested objects are not supported for filtering
3. **Use appropriate types** - numbers for numeric comparisons, booleans for true/false values
4. **Plan your taxonomy** - decide on standard values for categorical fields like `department` or `category`
5. **Don't over-filter** - metadata filtering happens after semantic search, so overly restrictive filters may exclude relevant results
