> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://www.comet.com/docs/opik/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://www.comet.com/docs/opik/_mcp/server.

# Exporting experiment results

In Opik 2.0, datasets and experiments are project-scoped. Pass a `project_name` when looking up an experiment by name so it resolves in the right project.

The experiments comparison page exports what you are looking at: select rows to export those, or export the
whole filtered result set with nothing selected. That file is built in your browser, so it is capped at
2,000 rows.

Above that, export with the SDK. It reads the result set through the API, so there is no practical size limit,
and the script below produces the same columns the comparison page does.

## Exporting an experiment to CSV

One row per dataset item, with the dataset fields, the evaluated output and every feedback score as its own
column — the shape the comparison page exports.

**`Python`**

```python title="Python"
import csv

import opik

client = opik.Opik()
experiment = client.get_experiments_by_name(
    name="my-experiment",
    project_name="my-project",
)[0]

rows = []
for item in experiment.get_items(max_results=100_000):
    row = {f"dataset.{key}": value for key, value in (item.dataset_item_data or {}).items()}
    row["output"] = item.evaluation_task_output

    for score in item.feedback_scores:
        row[f"feedback_scores.{score['name']}"] = score["value"]

    rows.append(row)

# Columns are data-driven - dataset fields and score names vary per item - so collect them from the rows.
columns = sorted({key for row in rows for key in row})

with open("experiment-results.csv", "w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=columns)
    writer.writeheader()
    writer.writerows(rows)
```

If you have the experiment's id — from its URL, for instance — `client.get_experiment_by_id("<id>")` fetches it
directly and needs no project name, since the id already identifies it. `get_experiments_by_name` returns every
experiment whose name contains the string you pass, which is why the example above takes the first result.

## What each item carries

| Field                    | Description                                                                            |
| ------------------------ | -------------------------------------------------------------------------------------- |
| `id`                     | The experiment item id                                                                 |
| `dataset_item_id`        | The dataset item this result belongs to, stable across experiments on the same dataset |
| `trace_id`               | The trace produced by the evaluation task                                              |
| `dataset_item_data`      | The dataset item's fields, as a dictionary                                             |
| `evaluation_task_output` | What the evaluated application returned                                                |
| `feedback_scores`        | Scores on the item, each with a `name`, `value` and optional `reason`                  |
| `assertion_results`      | Assertion outcomes, when the experiment defines assertions                             |

Two differences from the comparison page are worth knowing. Score reasons are available here as
`score["reason"]`, which the page exports as a separate `feedback_scores.<name>_reason` column — add it the same
way if you want it. Duration, token usage and estimated cost are not part of an experiment item: they belong to
the trace, so fetch the trace by `trace_id` when you need them.

`max_results` defaults to 10,000, so raise it for a larger experiment. Values come back untruncated and the whole
result set is held in memory at once, so a very large experiment is worth exporting in slices, by filtering the
dataset down.