---
title: "Ingest PPTX into Chroma with EnConvert"
description: "Use EnConvert to ingest a PPTX into Chroma as heading-aware chunks with section metadata, ready to add as documents and filter by section before vector search"
canonical: "https://www.enconvert.com/solutions/ingest-pptx-into-chroma-with-enconvert"
locale: "en"
---

# How to Ingest PPTX into Chroma with EnConvert

A developer building a RAG pipeline that ingests PPTX files into Chroma needs structured chunks where each part of a presentation carries its title and section context, not flat text that loses the deck’s structure. A library like python-pptx extracts content slide by slide, returning the text of each shape, and the slide title is a separate placeholder you have to find and attach to the text yourself. It also produces no section boundaries and no metadata a Chroma filter can operate on. For an agent reading a product pitch, a technical architecture deck, or a training presentation, flat slide text produces chunks the agent cannot locate within the presentation or filter by section. EnConvert’s Ingest endpoint takes the uploaded PPTX and converts each slide into a Markdown section before chunking. It returns JSONL chunks split at heading boundaries, each carrying its headings_path breadcrumb, section name, and chunk index in metadata, so structured PPTX chunks are ready to add to Chroma and retrieve in one job.

## Example

```python
import json
import os
import time

import chromadb
import requests
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction

ENCONVERT_API_KEY = os.environ["ENCONVERT_API_KEY"]
HEADERS = {"X-API-Key": ENCONVERT_API_KEY}

# Step 1: Upload the PPTX (the multipart field is named "files")
# Each slide is converted to a "## Slide N: Title" Markdown section before chunking
with open("/path/to/your/architecture-overview.pptx", "rb") as f:
    upload = requests.post(
        "https://api.enconvert.com/v2/ingest/files",
        headers=HEADERS,
        files={
            "files": (
                "architecture-overview.pptx",
                f,
                "application/vnd.openxmlformats-officedocument.presentationml.presentation",
            )
        },
        timeout=60,
    )
upload.raise_for_status()
job_id = upload.json()["job_id"]

# Step 2: Poll until the job completes
while True:
    status_response = requests.get(
        f"https://api.enconvert.com/v2/ingest/{job_id}",
        headers=HEADERS,
        timeout=30,
    )
    status_response.raise_for_status()
    job = status_response.json()
    if job["status"] == "completed":
        break
    if job["status"] in ("failed", "canceled"):
        raise RuntimeError(job.get("error_message") or f"Ingest job {job['status']}")
    time.sleep(2)

# Step 3: Download the JSONL output, one chunk per line
# The signed URL expires after 15 minutes. Plain GET, no API key.
jsonl_response = requests.get(job["output_url"], timeout=60)
jsonl_response.raise_for_status()

chunks = [
    json.loads(line)
    for line in jsonl_response.content.decode("utf-8").split("\n")
    if line.strip()
]
print(f"Loaded {len(chunks)} chunks ready for embedding")
print("Sections:", sorted({chunk["metadata"]["section"] for chunk in chunks}))

# Step 4: Connect to Chroma and create or open the collection
client = chromadb.PersistentClient(path="./chroma_db")

embedding_function = SentenceTransformerEmbeddingFunction(
    model_name="all-MiniLM-L6-v2"
)

collection = client.get_or_create_collection(
    name="pptx_chunks",
    embedding_function=embedding_function,
)

# Step 5: Add each chunk to Chroma with its metadata preserved
# headings_path is a list and can be empty, so join it into one string
# (Chroma does not accept empty lists as metadata values)
collection.add(
    ids=[chunk["id"] for chunk in chunks],
    documents=[chunk["content"] for chunk in chunks],
    metadatas=[
        {
            "section": chunk["metadata"]["section"],
            "headings_path": " > ".join(chunk["metadata"]["headings_path"]),
            "chunk_index": chunk["metadata"]["chunk_index"],
            "title": chunk["metadata"]["title"],
            "source_url": chunk["metadata"]["source_url"],
        }
        for chunk in chunks
    ],
)
print(f"Added {len(chunks)} chunks to Chroma")

# Step 6: Similarity search narrowed to one section by a metadata filter
# Replace with a section printed above. Slide sections look like "Slide 3: Title",
# and speaker notes are their own "Notes" section.
SECTION = "Slide 3: Service Authentication"

results = collection.query(
    query_texts=["how do services authenticate with each other"],
    n_results=3,
    where={"section": SECTION},
    include=["documents", "metadatas", "distances"],
)

for doc, meta, distance in zip(
    results["documents"][0], results["metadatas"][0], results["distances"][0]
):
    print(f"{meta['headings_path']} (distance {distance:.3f})")
    print(doc[:200])
```

## Output

Ingest converts the PPTX to Markdown sections and returns JSONL chunks split at heading boundaries, each carrying its headings_path breadcrumb, section name, and chunk index, so Chroma metadata filters can narrow retrieval to a specific section before vector search runs. Extracting content with python-pptx gives flat text per slide, with the slide title left as a separate placeholder to attach yourself and no metadata Chroma can filter on. The Ingest endpoint processes text-based slides only; image-only slides and slides with no text content are skipped.

## Get started

[Get free API key — 500 ops per month, no card required.](https://www.enconvert.com/auth?mode=signup)

## Related solutions

- [How to Ingest Markdown Files into LlamaIndex with EnConvert](https://www.enconvert.com/solutions/ingest-markdown-files-into-llamaindex-with-enconvert.md)
- [How to Ingest Web Pages into Vertex AI with EnConvert](https://www.enconvert.com/solutions/ingest-web-pages-into-vertex-ai-with-enconvert.md)
- [How to Ingest Markdown into OpenAI File Search with EnConvert](https://www.enconvert.com/solutions/ingest-markdown-into-openai-file-search-enconvert.md)
- [How to Ingest HTML Files into LlamaIndex with EnConvert](https://www.enconvert.com/solutions/ingest-html-files-into-llamaindex-with-enconvert.md)
- [Best Browserless Alternative for Document Parsing](https://www.enconvert.com/solutions/browserless-alternative-for-document-parsing-enconvert.md)
- [How to Ingest DOCX into CrewAI with EnConvert](https://www.enconvert.com/solutions/ingest-docx-into-crewai-with-enconvert.md)
