LlamaIndex SimpleDirectoryReader — file type, encoding, and metadata errors (2026) — Fix Guide (2026) | AI Error Hub
Home Providers LlamaIndex Directory reader errors
LlamaIndex Ingestion · Readers Severity: Medium HTTP n/a

LlamaIndex SimpleDirectoryReader — file type not recognized or metadata lost

<code>SimpleDirectoryReader</code> is the default file-system reader. It handles the common cases but has silent failure modes on unusual encodings, unrecognized extensions, and metadata that developers need to be explicit about.

By Ahmed R. · Senior AI Infrastructure Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: SimpleDirectoryReader auto-detects file types and delegates to per-format readers. Fix issues by (a) explicitly configuring file_extractor for non-standard file types, (b) handling encoding failures with encoding="utf-8" and error strategies, (c) using file_metadata callback to preserve custom metadata, and (d) using per-format readers (PyMuPDFReader, UnstructuredReader) for files that need more processing than the default.

Real error messages you'll see

These are the exact strings returned by the LlamaIndex framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.

File type not recognized
ValueError: File type .parquet is not supported. Consider providing a custom file_extractor or use a supported file type.
Encoding error mid-read
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa3 in position 1247: invalid start byte
# Windows-1252 file being read as UTF-8
Metadata missing after reading
# Document loaded but doc.metadata is {} — no source path, no file name,
# no custom fields the downstream code expected

Reference

Default file types SimpleDirectoryReader handles

ExtensionReader usedNotes
.pdfPyPDFReader / PDFReaderText only; images lost
.docxDocxReaderRequires python-docx
.pptxPptxReaderSlide text
.mdMarkdownReaderPreserves structure
.txtBasic file readerEncoding-sensitive
.htmlHTMLTagReaderText-only extraction
.csvCSVReaderRow-per-node option
.jsonNot native — needs custom extractor
.epubEpubReaderAvailable in community
.jpg, .pngImageReader with multimodal LLMRequires vision-capable LLM

Root causes, ranked by frequency

Based on developer reports across LlamaIndex forums, GitHub issues, and Discord community during 2025–2026.

  • 26%
    File type not in the default extractor map. .parquet, .yml, .rst, .xml — no default reader.
  • 18%
    Non-UTF-8 encoding. Windows / older docs in cp1252, latin-1, or shift-jis.
  • 14%
    PDF is image-only (scanned). Default PDF reader extracts empty text; needs OCR.
  • 10%
    Custom metadata not attached. file_metadata callback missing; downstream code assumes fields exist.
  • 8%
    docx / pptx / epub package not installed. Optional deps missing; reader silently skips file type.
  • 7%
    File path with unicode characters. On some OS + Python combos, paths with non-ASCII chars fail to open.
  • 10%
    Recursive read finds too many files. Massive directory tree; readers pulled into memory all at once.
  • 7%
    .gitignore-style files included. Reader picks up hidden files, lock files, temp files.

Fixes — copy-paste solutions

Fix #1

Configure file_extractor for non-standard file types

Point specific extensions to custom or community readers.

Pass a file_extractor dict mapping extensions to reader instances. Any extension not in the map either falls back to default or raises, depending on required_exts.

custom_file_extractor.py
from llama_index.core import SimpleDirectoryReader, Document
from llama_index.readers.file import PyMuPDFReader
from typing import List
import json

# Custom reader for .json files (not in defaults)
from llama_index.core.readers.base import BaseReader

class JSONReader(BaseReader):
    def load_data(self, file, extra_info=None) -> List[Document]:
        with open(file, "r", encoding="utf-8") as f:
            data = json.load(f)
        # Serialise back to text for embedding
        text = json.dumps(data, indent=2, ensure_ascii=False)
        return [Document(text=text, metadata={"source": str(file), **(extra_info or {})})]

# Custom reader for .parquet
class ParquetReader(BaseReader):
    def load_data(self, file, extra_info=None) -> List[Document]:
        import pyarrow.parquet as pq
        table = pq.read_table(file)
        df = table.to_pandas()
        # Emit one document per row (or aggregate — your choice)
        docs = []
        for i, row in df.iterrows():
            text = "\n".join(f"{col}: {val}" for col, val in row.items())
            docs.append(Document(text=text, metadata={
                "source": str(file),
                "row_index": i,
                **(extra_info or {}),
            }))
        return docs

reader = SimpleDirectoryReader(
    input_dir="./data",
    recursive=True,
    file_extractor={
        ".json": JSONReader(),
        ".parquet": ParquetReader(),
        ".pdf": PyMuPDFReader(),        # override default with PyMuPDF for better extraction
    },
    required_exts=[".pdf", ".docx", ".md", ".txt", ".json", ".parquet"],
    exclude_hidden=True,
    filename_as_id=True,
)

documents = reader.load_data()
print(f"Loaded {len(documents)} documents")
for doc in documents[:3]:
    print(f"  {doc.metadata.get('source', '?')} -> {len(doc.text)} chars")
For PDFs specifically, PyMuPDFReader extracts better than the default in most cases and preserves layout metadata. For scanned PDFs (image-only), use UnstructuredReader or an OCR-based extractor.
Fix #2

Handle encoding correctly for legacy text files

Non-UTF-8 files require explicit encoding.

Pass encoding= explicitly to file readers. For unknown encoding, use chardet to detect, or use errors="replace" as a safety net.

encoding_safety.py
from llama_index.core import SimpleDirectoryReader, Document
from llama_index.core.readers.base import BaseReader
import chardet

class SafeTextReader(BaseReader):
    """Text reader that detects encoding and falls back gracefully."""

    def load_data(self, file, extra_info=None):
        # First, detect encoding
        with open(file, "rb") as f:
            raw = f.read()
        detected = chardet.detect(raw)
        encoding = detected["encoding"] or "utf-8"
        confidence = detected["confidence"]

        try:
            text = raw.decode(encoding)
        except UnicodeDecodeError:
            # Fallback: replace un-decodable chars
            text = raw.decode(encoding or "utf-8", errors="replace")

        return [Document(
            text=text,
            metadata={
                "source": str(file),
                "encoding": encoding,
                "encoding_confidence": confidence,
                **(extra_info or {}),
            },
        )]

reader = SimpleDirectoryReader(
    input_dir="./legacy_data",
    recursive=True,
    file_extractor={".txt": SafeTextReader(), ".log": SafeTextReader()},
)

docs = reader.load_data()

# Audit encoding detection
low_confidence = [d for d in docs if d.metadata.get("encoding_confidence", 1) < 0.9]
if low_confidence:
    print(f"{len(low_confidence)} documents had low-confidence encoding detection")
    for d in low_confidence[:5]:
        print(f"  {d.metadata['source']} -> {d.metadata['encoding']} "
              f"({d.metadata['encoding_confidence']:.0%})")
Encoding detection is imperfect. For known-legacy sources (mainframe exports, Windows logs), hardcode the encoding rather than trusting chardet. Log the detected encoding so you can audit later.
Fix #3

Preserve rich metadata with file_metadata callback

The metadata callback is where you inject source-aware context.

Pass a callable to file_metadata. It receives each file path and returns a dict merged into every document from that file. Perfect for adding department, category, freshness, or source-type tags.

metadata_callback.py
from pathlib import Path
from datetime import datetime
from llama_index.core import SimpleDirectoryReader

def file_metadata_fn(file_path: str) -> dict:
    """Attach metadata based on file location and stats."""
    p = Path(file_path)
    stat = p.stat()

    # Derive category from directory structure
    parts = p.parts
    category = parts[-3] if len(parts) >= 3 else "uncategorized"

    return {
        "file_name": p.name,
        "file_path": str(p.resolve()),
        "file_type": p.suffix.lstrip("."),
        "file_size_bytes": stat.st_size,
        "created_at": datetime.fromtimestamp(stat.st_ctime).isoformat(),
        "modified_at": datetime.fromtimestamp(stat.st_mtime).isoformat(),
        "category": category,
        # Add your own domain-specific tags
        "department": "engineering" if "eng" in str(p).lower() else "other",
    }

reader = SimpleDirectoryReader(
    input_dir="./docs",
    recursive=True,
    file_metadata=file_metadata_fn,   # ← attaches to every doc
    exclude_hidden=True,
    exclude=[".git", "node_modules", "__pycache__", "*.tmp"],
    required_exts=[".md", ".pdf", ".txt"],
)

docs = reader.load_data()

# Metadata flows through the whole pipeline —
# available on retrieved nodes, so downstream can filter/cite by category etc.
for doc in docs[:3]:
    print(doc.metadata)

# On queries, metadata is preserved on the nodes for filter push-down:
from llama_index.core.vector_stores import MetadataFilter, MetadataFilters
filters = MetadataFilters(filters=[
    MetadataFilter(key="category", value="policies"),
    MetadataFilter(key="department", value="engineering"),
])
# Use filters on the query engine (see page 109)
Metadata attached at ingest time is available at query time for filtering. Design the metadata schema upfront — retro-fitting requires re-embedding.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Explicitly configure file_extractor for every file type you expect.
  • Use required_exts to whitelist supported extensions; fail loud on unexpected ones.
  • For non-UTF-8 sources, detect encoding at read time and store the encoding in metadata.
  • Attach source-aware metadata with file_metadata=callback — enables filter push-down later.
  • For PDFs, prefer PyMuPDFReader or UnstructuredReader over the default.
  • Use exclude= to skip .git, lock files, and temp files.
  • Audit the loaded document count against the expected file count — silent skips are the biggest ingestion risk.

Frequently asked questions

Not directly — use the dedicated readers S3Reader, GCSReader, or AzureBlobStorageReader from llama-index-readers-* packages. They provide the same interface but authenticate against cloud storage.
Use UnstructuredReader with strategy="hi_res" (requires tesseract) or a dedicated OCR reader. For LlamaIndex-native OCR, wrap ImageReader with a vision-capable LLM.
Document is the input unit — one file or logical document. Nodes are the split pieces used for embedding and retrieval. The node parsers turn documents into nodes.
Yes with iter_data() instead of load_data() — returns an iterator. Useful for very large corpora where holding everything in memory is impractical.
Each returned Document can have identical metadata but distinct doc_id. For explicit doc-id control, use filename_as_id=True or set doc.id_ = "custom-id" after loading.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.