> ## Documentation Index
> Fetch the complete documentation index at: https://docs.speckle.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Export model data to CSV

> Export door schedules, room lists, and property reports from Speckle model data to CSV

## Goal

Export structured model data from a Speckle version to a CSV file you can open in Excel — door schedules, room lists, or custom property reports.

## What you will build

A script that loads a model, finds doors (or rooms), extracts name, level, type, and fire rating from `DataObject.properties`, and writes `door-schedule.csv`.

## When to use this

Use this when a BIM manager, architect, or engineer needs a **spreadsheet report** from published model data — schedules, QA lists, or quantity exports. This extends the pattern from [Build your first model analysis tool](/developers/sdks/dotnet/guides/build-your-first-model-analysis-tool).

## Recommended approach

1. Load the latest version with `Receive2` (see [Automate with scripts](/developers/sdks/dotnet/getting-started/scripts-and-notebooks))
2. `Flatten()` the object graph once
3. Filter `DataObject` instances by `properties["category"]` or similar
4. Read BIM fields from `properties` — not from typed C# classes
5. Write CSV with `StreamWriter`, escaping commas in cell values

Avoid this unless you need Excel-specific formatting — for most reports, CSV is the simplest path.

## Complete example

```csharp Complete example lines icon="https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251" expandable theme={null}
using Speckle.Objects.Data;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Extensions;

static string? GetProp(DataObject obj, params string[] keys)
{
    foreach (var key in keys)
        if (obj.properties.TryGetValue(key, out var val) && val is string s)
            return s;
    return null;
}

static bool IsDoor(Base obj) =>
    obj is DataObject d &&
    (GetProp(d, "category", "builtInCategory")?.Contains("Door", StringComparison.OrdinalIgnoreCase) ?? false);

var doors = root.Flatten().Where(IsDoor).Cast<DataObject>().ToList();

var outputPath = "door-schedule.csv";
await using (var writer = new StreamWriter(outputPath))
{
    await writer.WriteLineAsync("Name,Level,Type,FireRating");
    foreach (var door in doors)
    {
        var name = door.name ?? "";
        var level = GetProp(door, "level", "Level") ?? "";
        var type = GetProp(door, "type", "family", "Family") ?? "";
        var fireRating = GetProp(door, "fireRating", "Fire Rating") ?? "";

        static string CsvCell(string value) =>
            value.Contains(',') || value.Contains('"') ? $"\"{value.Replace("\"", "\"\"")}\"" : value;

        await writer.WriteLineAsync(
            $"{CsvCell(name)},{CsvCell(level)},{CsvCell(type)},{CsvCell(fireRating)}");
    }
}

Console.WriteLine($"Exported {doors.Count} doors to {outputPath}");
```

### Room schedule variant

Filter on `category` containing `Room` and export `name`, `area`, and `level`:

```csharp Example lines icon="https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251" theme={null}
var rooms = root.Flatten()
    .OfType<DataObject>()
    .Where(d => GetProp(d, "category")?.Contains("Room", StringComparison.OrdinalIgnoreCase) == true);

foreach (var room in rooms)
{
    var area = room.properties.GetValueOrDefault("area")?.ToString() ?? "";
    // write name, area, level to CSV
}
```

<Note>
  Property names vary by connector and host app. Inspect a sample object in the Speckle viewer or use [Find objects by property](/developers/sdks/dotnet/guides/finding-and-extracting-data) to discover the keys your model uses.
</Note>

## How it works

Connector-published BIM data is stored as **`DataObject`** with semantics in **`properties`**. There are no typed `Door` or `Room` classes in Speckle Object Model v3 — you filter and read dictionaries.

`Flatten()` gives you every object in one pass. Build your CSV from that list rather than walking the hierarchy manually unless you need parent context.

## Common mistakes

| Mistake                                               | Fix                                                |
| ----------------------------------------------------- | -------------------------------------------------- |
| Assuming property names match Revit UI labels exactly | Log `properties.Keys` from one sample object first |
| Checking `speckle_type` for "Wall" or "Door"          | Filter on `properties["category"]` instead         |
| Not escaping commas in CSV cells                      | Wrap values in quotes when they contain commas     |
| Calling `Flatten()` per row                           | Flatten once, then filter                          |

## Next steps

<CardGroup cols={3}>
  <Card title="Find objects by property" icon="magnifying-glass" href="/developers/sdks/dotnet/guides/finding-and-extracting-data">
    Filter, index, and QA checks
  </Card>

  <Card title="BIM data patterns" icon="building" href="/developers/sdks/dotnet/guides/bim-data-patterns">
    How connector data is structured
  </Card>

  <Card title="Send analysis results back" icon="upload" href="/developers/sdks/dotnet/guides/send-analysis-results-back">
    Publish QA results to Speckle
  </Card>
</CardGroup>
