Skip to main content
Loaders are responsible for reading files from your disk or cloud storage and converting them into plain text that Cognee can process. When you run remember(), Cognee automatically selects the most appropriate loader for each file based on its extension and content type.

Loader Selection

Cognee uses a priority system to decide which loader to use. It tries to match a loader in the following order:
  1. CodeLoader: For source-code files (.py, .ts, .go, .rs, etc.). It sits ahead of TextLoader because code content-sniffs as plain text, so TextLoader would otherwise claim it first.
  2. TextLoader: For plain text files (.txt, .md, .json, .xml, etc.).
  3. PyPdfLoader: For PDF files (requires pypdf).
  4. ImageLoader: For images (uses vision models to transcribe content, with an optional local OCR pass).
  5. AudioLoader: For audio files (uses transcription models).
  6. VideoLoader: For video files (transcribes the audio track with inline [HH:MM:SS] timestamps).
  7. DltCsvLoader: For CSV files (routes rows through dlt structured ingestion instead of text). It sits above CsvLoader, so this is the default route for every .csv.
  8. CsvLoader: For CSV files (converts rows to text).
  9. UnstructuredLoader: For complex formats like .docx, .pptx, .epub (requires unstructured).
  10. AdvancedPdfLoader: For layout-aware PDF extraction (requires unstructured).
  11. DoclingLoader: If no other loader can ingest the file type, Docling is used for conversion (if the type is supported).
If you want to force a specific loader or provide custom configuration, you can use the preferred_loaders parameter in remember().

Available Loaders

Core Loaders

These are available by default in every Cognee installation:
  • CodeLoader: Claims source-code files by file-name extension and stores them verbatim under their original extension. Files it claims take the deterministic code graph pipeline during cognify() instead of LLM-based extraction. See the “Code Loader” accordion below.
  • TextLoader: Reads text files with UTF-8 encoding. It is also the content-detection fallback, so a file whose bytes sniff as plain text but are not decodable is refused with a typed error rather than an internal failure — see the note below.
  • DltCsvLoader: Ingests .csv files through the dlt structured path instead of flattening them to text — the rows are loaded into a dlt staging database and the loader emits one manifest per CSV file, which cognify turns into row nodes deterministically from the schema, with no LLM extraction. dlt is a core cognee dependency, so this loader registers on every install and, sitting above CsvLoader in the priority order, claims every CSV by default.
  • CsvLoader: Reads CSV files and converts each row into a structured text format (Row N: key: value). Because DltCsvLoader outranks it, this loader is reached only when you ask for it per call with preferred_loaders=[{"csv_loader": {}}].
  • ImageLoader: Uses an LLM vision model to transcribe the image content. By default the transcription uses an extraction-oriented prompt that asks for entities, relationships, verbatim text, and structured content (tables, charts, diagrams) rather than a short caption. An optional local OCR pass can be enabled to append recognized text to the transcription; it requires pip install cognee[rapidocr]. See the “Image transcription and OCR” accordion below.
  • AudioLoader: Uses an audio transcription API to transcribe audio files. This depends on your configured LLM provider supporting transcription endpoints; see LLM Providers for provider-specific caveats.
  • VideoLoader: Transcribes a video’s audio track into text with inline [HH:MM:SS] segment timestamps, then feeds it through the normal text pipeline. .mp4 and .webm do not require ffmpeg; other containers do (see the VideoLoader usage accordion below).
Media loaders need a usable LLM. ImageLoader, AudioLoader, and VideoLoader are the one ingestion step that calls the LLM — there is no LLM-free vision or transcription path — so each one checks before it touches the file.
“Usable” is the same rule the provider consistency preflight applies, not a literal LLM_API_KEY check: a provider that needs no key of its own — bedrock, llama_cpp, azure with managed identity — or a live MCP sampling session counts as configured. With no usable LLM, load() raises LLMAPIKeyNotSetError naming the file kind and the work it was about to do:
AudioLoader says “Audio ingestion needs an LLM: cognee transcribes audio with the LLM before indexing it”, and VideoLoader says “Video ingestion needs an LLM: cognee transcribes the video’s audio track with the LLM before indexing it”. On the CLI and the HTTP API the error also carries its remediation hint, so what you see there ends with a further Fix: Set LLM_API_KEY in your .env .... A path that does not exist still reports FileNotFoundError first, so a typo is not reported as a missing key. Past that, the check runs before the loader reads the file or writes any derived text, so a run that cannot finish leaves nothing behind. Text, document, and code files are unaffected: they ingest with no key at all, using the local GLiNER demo extractor and a local embedder.
TextLoader refuses bytes it cannot decode. Because TextLoader is the content-detection fallback, a binary file whose bytes the sniffer reads as text/plain — a .dmg, an archive — lands on it. When decoding fails, load() now raises UnreadableFileContentError instead of letting a UnicodeDecodeError escape as an internal error.
The message names the file, the encoding it tried, the decode reason and the byte offset it failed at, and points at the formats that do work:
Over the HTTP API this is a 415 Unsupported Media Type rather than a 500, so integrators should treat it as terminal and stop retrying the upload — see 415 Unsupported Media Type. Every route that ingests an uploaded file answers this way: POST /api/v1/add, POST /api/v1/remember, and PATCH /api/v1/update. An add sent with run_in_background=true is the exception, since it answers before ingestion runs: the refusal lands on the pipeline run there, not on the response. This is a different case from the extension table’s note below, which covers a file no registered loader claims at all.

External Loaders

These require additional dependencies to be installed:
  • PyPdfLoader: Extracts text from PDFs page by page using the pypdf library, preserving page boundaries with Page N: markers in the extracted text. Requires pip install cognee[docs] (or pip install pypdf).
  • AdvancedPdfLoader: Layout-aware PDF extraction using the unstructured library. Extracts text, tables (as HTML), and image placeholders per page. Falls back to PyPdfLoader automatically if extraction fails. Requires pip install cognee[docs], plus poppler and tesseract installed on the system.
  • UnstructuredLoader: Handles many office and document formats (.docx, .xlsx, .pptx, .odt, .rtf, .eml, .epub, .html, and more) via unstructured’s auto-partition. Requires pip install cognee[docs].
  • BeautifulSoupLoader: Extracts text from HTML files using BeautifulSoup. Applies CSS selector rules to pull structured content from specific tags. Requires pip install cognee[scraping].
  • DoclingLoader: Catch-all fallback that converts a wide range of document formats (PDF, DOCX, XLSX, PPTX, HTML, Markdown, and more) to plain text via Docling. Supported extensions are discovered dynamically from Docling’s FormatToExtensions map. Requires pip install cognee[docling].

Supported File Extensions

Cognee selects loaders based on file type. The table below shows the supported extensions and their default loaders. Extensions are matched case-insensitively for the text-only formats that carry no content signature — .txt, .csv, .md, .json, .xml, .yaml, and .yml — so REPORT.CSV and report.csv are detected as the same type and get the same loader. .log files are case-insensitive too: an unrecognized extension like .LOG falls back to plain text, which lands on the same TextLoader. Formats identified from their content, such as PDFs, images, audio, and video, were never case-sensitive.
Note: Files with extensions not in this table cannot be remembered by default. Use a custom loader to handle additional formats. When no registered loader can handle a file, remember() raises a ValueError that names the file’s extension and lists the currently supported extensions. For office and document formats that ship only via an optional loader (for example .pptx, .docx, .xlsx, .html, .epub), install cognee[docling] for Docling or cognee[docs] for Unstructured-backed loaders, then retry.

Usage

You can override the default loader selection by specifying preferred_loaders. This is useful when you want to pass specific configuration options to a loader.
Opting out of the dlt CSV route. DltCsvLoader outranks CsvLoader for every .csv file on every install, so naming csv_loader explicitly is the only way to get text flattening:
The same channel carries per-call dlt options — primary_key, write_disposition, max_rows_per_table, and column_value_columns — to DltCsvLoader:
CodeLoader is a core loader that claims source-code files so they take Cognee’s deterministic code graph pipeline instead of the LLM extraction path.
Matched by extension only. Content sniffing cannot identify a programming language — it reports code as plain text — so CodeLoader looks at the file-name extension and ignores the detected MIME type entirely. It claims these 31 extensions:.c, .cc, .cpp, .cs, .cxx, .dart, .fs, .go, .h, .hcl, .hh, .hpp, .java, .js, .jsx, .kt, .kts, .php, .proto, .py, .rake, .rb, .rs, .scala, .svelte, .swift, .tf, .ts, .tsx, .vb, .vueExtensions that double as generic config or markup are deliberately not claimed — .yaml and .yml (Ansible), .json (OpenAPI), and template formats such as .xaml, .razor, .cshtml, and .hbs. Claiming those would hijack ordinary documents, so files with those extensions keep going to TextLoader as before. .graphql is also left out, for a different reason: the extractor only reads GraphQL schemas inside a larger project, not as lone files.Stored under the real extension. Like TextLoader, CodeLoader stores content verbatim under a content-hash file name, but it keeps the original suffix — code_<content_hash>.<ext> (for example code_a1b2c3....py). The storage file name is the only place the extension survives, and language detection downstream depends on it.No LLM or embedding calls. Claimed files are classified as code files and routed by cognify() down a dedicated code route whose task list runs the code graph extraction directly. Chunking, entity extraction with an LLM, and summarization do not run for them, so they also cost nothing in a cognify(dry_run=True) estimate — see Dry-run cost estimation.Requires the enola binary. The code graph extraction is performed by enola, an external Go CLI, not by Python code inside Cognee. The binary ships with Cognee: enola-cli==0.4.26 is a core dependency, so a plain pip install cognee already provides it and nothing is downloaded at run time. Set ENOLA_PATH to use a binary you installed yourself. If no binary can be found, the run raises EnolaNotInstalledError asking you to reinstall cognee. See the “Where the enola binary comes from” accordion below for the exact resolution order.Opting out. preferred_loaders is tried before the default priority order, so you can send a code file back down the normal text and LLM path:
It ships with Cognee. enola is distributed as the enola-cli wheel, pinned to 0.4.26 in Cognee’s core dependencies — installing Cognee installs it, and no extra is involved. The version is pinned deliberately: Cognee reads enola’s snapshot contract and validates the known answers of the code graph against that release. Nothing is downloaded at run time.Resolution order. Cognee looks for the binary in three places, in this order:The scripts directory is checked before PATH because it is only on PATH when the virtual environment is activated — running the interpreter by path (a container entrypoint, a service unit, a scheduler) would otherwise miss the bundled binary.When nothing is found. The run raises EnolaNotInstalledError: “The code graph feature needs the enola-cli package, which is included with cognee. Reinstall cognee to restore it.” If ENOLA_PATH is set but no file exists at that path, the same error is raised rather than silently falling back to the bundled binary.Using your own build. Point ENOLA_PATH at it to override the bundled version:
Cognee runs the subprocess with enola’s own update check disabled, so the extraction itself makes no network calls.For the repository-level code graph pipeline that uses this binary, see the Code Graph guide.
AdvancedPdfLoader uses the unstructured library to perform layout-aware extraction, preserving page structure, tables, image metadata, and page numbers. It groups content by page and prepends Page N: markers to each page’s extracted text, making source pages traceable in downstream chunks. Because PyPdfLoader has higher default priority, you need to request it explicitly with preferred_loaders. It accepts a strategy parameter that controls the trade-off between speed and accuracy:If you want to inspect those page markers after ingestion, retrieve raw chunks with SearchType.CHUNKS. The page number is kept in the chunk text, not in a separate metadata field.
Make sure poppler and tesseract are installed on your system before using AdvancedPdfLoader, in addition to installing the Python dependencies with pip install cognee[docs].
Cognee does not automatically distinguish scanned (image-only) PDFs from text PDFs. The default PyPdfLoader reads the embedded text layer page by page and skips pages with no extractable text. For a scanned PDF — where each page is an image with no text layer — this silently produces empty or near-empty output, and no OCR is performed.A text layer is the selectable, machine-readable text embedded in the PDF. If a page has no text layer, PyPdfLoader omits that page and no Page N: marker is added for it. If pypdf errors on a malformed page, Cognee logs a warning, skips that page, and continues loading the rest of the document.To process scanned PDFs, explicitly select AdvancedPdfLoader with an OCR strategy. Because PyPdfLoader has higher default priority, you must request the OCR-capable loader through preferred_loaders:
Use strategy="ocr_only" for fully image-based or scanned PDFs, and strategy="hi_res" for documents that mix a text layer with scanned images. OCR requires pip install cognee[docs] plus poppler and tesseract installed on the system. For non-English or CJK scanned documents, also install the matching Tesseract language packs (see the next accordion).
Standard PDF text extraction via PyPdfLoader or AdvancedPdfLoader with strategy="fast" often fails silently on CJK and other non-Latin documents. These PDFs commonly embed glyphs as images or use non-standard font encodings, which can lead to empty or garbled output.Use AdvancedPdfLoader with an OCR-based strategy and install Tesseract language packs for your target language.Step 1 — install Tesseract language packs (Ubuntu/Debian):
Step 2 — use ocr_only strategy with the languages parameter:
The languages list accepts ISO 639-2 Tesseract language codes. Common values:Use strategy="hi_res" for better layout accuracy when the document has mixed text and images, and strategy="ocr_only" for fully image-based or scanned PDFs.
If you also need translation after OCR extraction, use the Multilingual Ingestion pipeline before building the knowledge graph.
UnstructuredLoader handles a wide range of office and document formats using unstructured’s auto-partition feature. It supports the same strategy options as AdvancedPdfLoader.Supported file types:
DoclingLoader is the lowest-priority loader and acts as a catch-all fallback for any file type that no other loader can ingest. It converts the document with Docling and exports plain text via Docling’s export_to_text(). Supported extensions are pulled at runtime from Docling’s FormatToExtensions (PDF, DOCX, XLSX, PPTX, HTML, Markdown, and more), so the available formats track your installed Docling version.Install with pip install 'cognee[docling]'. Because it sits last in the loader priority, Cognee only reaches for it when no higher-priority loader matches — to force it on a file another loader would normally handle (for example, to use Docling’s layout-aware parsing on a PDF instead of PyPdfLoader), pass it through preferred_loaders:
When to use Docling vs. AdvancedPdfLoader: prefer AdvancedPdfLoader for PDFs when you need per-page markers, HTML-formatted tables, or OCR strategy control (see the Advanced PDF Loader accordion above). Reach for DoclingLoader when you want a single unified converter across many formats, or for non-PDF files Cognee does not otherwise handle.
BeautifulSoupLoader parses HTML files using CSS selectors. By default it applies a comprehensive set of extraction rules covering common HTML content areas (headings, paragraphs, articles, tables, code blocks, etc.). You can pass your own extraction_rules dict to target specific elements.Each rule is a dict with the following optional keys:
Overlapping CSS rules are deduplicated. Rules are applied in the order they appear in extraction_rules, against a single parsed document. Once a rule extracts an element, elements nested inside it are skipped by later rules, so content covered by a broad selector is not emitted a second time by a narrower one. With the default rules, for example, article runs before paragraphs, so a <p> inside an <article> is extracted once as part of the article rather than repeated. Deduplication works in one direction only: a broad rule listed after a narrower one is not suppressed by it and re-emits the shared content anyway, so list broader selectors before narrower ones — as the default rules do — to avoid duplicates.Deduplication only skips descendants of already-extracted elements. Two rules whose selectors match the exact same element still extract that element twice. Matches within a single rule are likewise not deduplicated against each other, so one selector matching both an element and its descendant emits the shared text twice.For XPath-based extraction (requires pip install lxml):
XPath rules are evaluated against a separate lxml tree and do not take part in the deduplication described above. An XPath rule never suppresses a later CSS rule, and it is never suppressed by an earlier one — so mixing XPath and CSS rules that cover the same elements can still repeat content.
ImageLoader uses a vision-capable LLM to describe image content. By default that is your LLM_MODEL; set IMAGE_TRANSCRIBE_MODEL to send images to a different vision model while keeping a text-only (or cheaper) model for everything else. The image model uses the same provider, endpoint, and API key as LLM_MODEL.Vision-capable models by provider:
If the model used for images (IMAGE_TRANSCRIBE_MODEL, or LLM_MODEL when that is unset) does not support vision, remembering an image file will fail at the description step. Point either one at a vision-capable model and retry.
Ollama users: Pull a vision-capable model (e.g. ollama pull llava) and set IMAGE_TRANSCRIBE_MODEL="llava" to keep a text-only chat model such as llama3.1 for everything else, or set LLM_MODEL="llava" to use it for both. Text-only models cannot process images. See LLM Providers for full Ollama setup.
For the prompt, token cap, and optional OCR pass used during transcription, see the “Image transcription and OCR” accordion below.
ImageLoader turns an image into text in two steps: a vision-LLM transcription, and — when enabled — a local OCR pass whose recognized text is appended to that transcription. Both are controlled by environment variables.Extraction-oriented transcription (default). The default prompt asks the vision model for concise, factual text aimed at graph extraction: the entities shown and their attributes, the relationships between them, all visible text, numbers, dates, and labels transcribed verbatim, and structured content (tables as rows, charts as series and data points, diagrams as how the elements connect). This produces longer text than a caption, which is why the token cap defaults to 1024.
Optional local OCR. OCR is off by default. When switched on, Cognee runs RapidOCR locally — a pip-only dependency, no system binary — and appends the recognized text to the vision transcription under an [OCR extracted text] heading:
Because the OCR text is part of the loaded text, it flows through chunking, extraction, and storage like any other content. This is most useful for screenshots, scanned pages, and dense charts where the vision model paraphrases labels instead of reproducing them.
OCR text is truncated at 8000 characters (the tail is replaced with ...). If the OCR pass itself fails, Cognee logs an error and continues with the vision transcription alone rather than failing the ingestion.
Custom transcription prompt. IMAGE_TRANSCRIPTION_PROMPT_PATH accepts either a file name inside Cognee’s built-in prompt directory (cognee/infrastructure/llm/prompts) or an absolute path, so you can keep your own prompt file anywhere on disk:
Empty transcriptions on reasoning models. IMAGE_TRANSCRIPTION_MAX_COMPLETION_TOKENS caps reasoning tokens as well as output tokens, so on a reasoning model (including the default openai/gpt-5.6-luna) a small cap can consume the whole budget on reasoning and return empty content. Cognee logs a warning naming the file and suggesting a higher IMAGE_TRANSCRIPTION_MAX_COMPLETION_TOKENS, then continues with empty text for that image. If images ingest but contribute nothing to the graph, raise the cap.
VideoLoader ingests a video by transcribing its audio track and inlining per-segment [HH:MM:SS] timestamps into the resulting text, for example:
Because the timestamps are part of the text, they survive chunking and stay searchable. From there the transcript flows through the normal TextDocument pipeline (chunking, entity/relationship extraction, graph + vector storage) — there is no separate video document type, so a video becomes queryable memory with no special handling downstream.VideoLoader is a core loader, available in every installation. Supported extensions: .mp4, .m4v, .mov, .webm, .mkv, .avi.
ffmpeg requirement. .mp4 and .webm do not require any extra tooling: if ffmpeg is unavailable, Cognee can send those containers straight to the transcription endpoint. Other containers (.mov, .mkv, .avi, .m4v) require system ffmpeg on your PATH to extract the audio track first. When ffmpeg is present it is also used for .mp4/.webm to keep the upload small.If a container needs ffmpeg and none is found, remember() raises a RuntimeError explaining that ffmpeg must be installed and on your PATH, or that you can supply the video as .mp4 or .webm instead. There is no cognee[video] extra — install ffmpeg through your system package manager.
Inline [HH:MM:SS] markers require a transcription model that supports segmented (verbose_json) output, such as whisper-1. Providers or models without segmented output fall back to a plain transcript with no timestamp markers; the transcript is still ingested normally.
If you need to handle a custom file format, you can create your own loader class and register it with Cognee.
supported_extensions values must not include a leading dot — use bare extensions like "custom" and "jpeg", not ".custom" or ".jpeg". Loader matching compares against the file’s dot-free extension, so a dotted value would never match.
load() receives ingestion context. Beyond the loader-specific options you pass via preferred_loaders, Cognee forwards the current ingestion context to load() as keyword arguments: dataset_name, dataset_id, user, and original_file_name (the user’s real file name, which can differ from file_path when the bytes are a localized copy of an s3:// source or an upload). Accept **kwargs — as the example above does — and ignore what you don’t need.Returning a LoaderResult. load() normally returns the stored derived-text path as a plain str. A loader that also owns the record’s identity and routing can instead return a LoaderResult:
Omitting file_metadata (or returning a plain str) makes ingestion re-open the stored file to derive the hash, size, and mime type it needs for the Data row; over the S3 backend that read-back costs an extra HEAD plus a full GET of content the loader already had. Loaders that write their text through the store_derived_text helper get the field filled in for free — it stores and describes in one step.Returning a plain string remains fully supported; only loaders that need stable identity or custom routing need LoaderResult. The built-in DltCsvLoader uses it to carry a CSV manifest’s stable data_id and its no-LLM cognify route stamp.