# File extractors

A file extractor runs your own parsing code inside Nominal’s ingestion pipeline. You package the code as a Docker image. On each upload, Nominal mounts the input files in your container, runs it, and ingests the output it writes. That lets you support bespoke binary formats, proprietary logs, or custom transforms without leaving the normal upload flow. It also covers consistent post-processing of formats Nominal already parses, such as rewriting the timestamp column of otherwise valid Parquet.

## Where this fits

Nominal stores time-series data in [datasets](https://dev.nominal.io/platform/data-model.md#time-series-data), built from ingested files. Each ingest parses a file’s columns or records into [channels](https://dev.nominal.io/platform/data-model.md#channels), places samples in time using timestamp metadata, and optionally applies tags that partition the data within the dataset (per test run, per vehicle). Nominal parses CSV, Parquet, and MCAP natively, and has first-party extractors for [other formats](https://dev.nominal.io/file-ingestion.md), such as PX4 ULog and MAVLink telemetry logs. An extractor runs your code in that same pipeline and produces the same channels, tags, and files.

Use one when:

- the format is proprietary or unsupported, and only your code reads it;
- it recurs, so the parsing logic should live in one versioned place instead of on each engineer’s laptop;
- non-developers upload the files through the Nominal app, so the conversion has to happen without them running anything.

For a one-off conversion with a parser you already have, convert locally and upload the result as CSV or Parquet instead. An extractor pays for its setup through repeat use.

## Extractors and images

An extractor is created once and given one or more images over time. The extractor itself carries only identity: a name and description. Everything about *how* it executes, including its inputs, parameters, output format, and default timestamp metadata, lives on its registered images. Exactly one image is **active** at a time, and that is the image ingest runs. Shipping a new version means registering a new image and activating it, and the switch is atomic.

You give Nominal the image by uploading a `docker save` tarball, which Nominal hosts in its own internal registry. No external registry or credentials are required, and each registered image carries its own execution contract, so the registration can never drift from the image that runs.

## How it works

1. **Write and build** your container against [the container contract](https://dev.nominal.io/extractors/contract.md). In Python, [the extractor package](https://dev.nominal.io/extractors/python.md) implements the contract for you.
2. **[Register an image](https://dev.nominal.io/extractors/register.md)** with Nominal, declaring its inputs, parameters, output format, and default timestamp metadata, then activate it.
3. **[Run ingests](https://dev.nominal.io/extractors/ingest.md).** Nominal mounts the input files in the container, runs your active image, and ingests whatever it writes.

Write an extractor

- [The container contract](https://dev.nominal.io/extractors/contract.md)
- [Write an extractor in Python](https://dev.nominal.io/extractors/python.md)
- [Timestamps and tags](https://dev.nominal.io/extractors/timestamps-and-tags.md)

Use an extractor

- [Register an image](https://dev.nominal.io/extractors/register.md)
- [Run an ingest](https://dev.nominal.io/extractors/ingest.md)
- [Manage extractors and images](https://dev.nominal.io/extractors/manage.md)
