One of the most valuable things you can do with the Revit API is extract structured data from your models. Room areas, door schedules, structural member quantities, equipment asset lists — all of this data lives in the model but is locked in Revit's internal format. The API gives you direct access to query, filter and export it however you need.
This guide covers the core patterns: collecting elements, reading parameters, and writing the results to a CSV or database.
Setting Up a Revit API Project
Before writing any data extraction code, you need a basic Revit .NET plugin structure. Create a C# Class Library project in Visual Studio targeting .NET Framework 4.8, and reference these DLLs from your Revit installation folder:
RevitAPI.dll RevitAPIUI.dll
Add these as references — Copy Local: False
Your command class must implement IExternalCommand:
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
[Transaction(TransactionMode.ReadOnly)]
public class ExtractDataCommand : IExternalCommand
{
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
UIApplication uiApp = commandData.Application;
Document doc = uiApp.ActiveUIDocument.Document;
// Your extraction code goes here
return Result.Succeeded;
}
}Basic IExternalCommand structure — entry point for all Revit API commands
The FilteredElementCollector
FilteredElementCollector is the primary way to query elements from a Revit model. It applies server-side filtering which is significantly faster than iterating all elements manually.
// Collect all Rooms in the model
FilteredElementCollector collector =
new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Rooms)
.WhereElementIsNotElementType();
// Collect all Doors
FilteredElementCollector doors =
new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Doors)
.WhereElementIsNotElementType();
// Collect all structural columns
FilteredElementCollector columns =
new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_StructuralColumns)
.WhereElementIsNotElementType();FilteredElementCollector by category — always add WhereElementIsNotElementType() to exclude type definitions
Reading Parameters
Once you have elements, reading their parameter values is straightforward. You can access parameters by name, by BuiltInParameter enum, or by GUID (for shared parameters):
foreach (Element room in collector)
{
// Read built-in parameter
double area = room.get_Parameter(
BuiltInParameter.ROOM_AREA)?.AsDouble() ?? 0;
// Convert from internal Revit units (cubic feet) to m²
double areaSqM = UnitUtils.ConvertFromInternalUnits(
area, UnitTypeId.SquareMeters);
// Read parameter by name (custom / shared parameters)
string department = room.LookupParameter("Department")
?.AsString() ?? "";
string roomNumber = room.get_Parameter(
BuiltInParameter.ROOM_NUMBER)?.AsString() ?? "";
string roomName = room.get_Parameter(
BuiltInParameter.ROOM_NAME)?.AsString() ?? "";
// Use the data
Console.WriteLine($"{roomNumber} {roomName} — {areaSqM:F2} m² — {department}");
}Reading room parameters — note the unit conversion for area values
Revit stores all numeric values in internal units — typically decimal feet for length and square feet for area. Always convert using UnitUtils.ConvertFromInternalUnits() before displaying or storing values. Revit 2022+ uses UnitTypeId; earlier versions use DisplayUnitType.
Exporting to CSV
Writing extracted data to CSV is the quickest way to get it out of Revit:
using System.IO;
using System.Text;
string outputPath = @"C:\output
oom_data.csv";
var sb = new StringBuilder();
sb.AppendLine("Room Number,Room Name,Area (m²),Department,Level");
foreach (Element room in collector)
{
string number = room.get_Parameter(
BuiltInParameter.ROOM_NUMBER)?.AsString() ?? "";
string name = room.get_Parameter(
BuiltInParameter.ROOM_NAME)?.AsString() ?? "";
double area = UnitUtils.ConvertFromInternalUnits(
room.get_Parameter(BuiltInParameter.ROOM_AREA)
?.AsDouble() ?? 0,
UnitTypeId.SquareMeters);
string dept = room.LookupParameter("Department")
?.AsString() ?? "";
string level = (doc.GetElement(room.LevelId) as Level)
?.Name ?? "";
sb.AppendLine($"{number},{name},{area:F2},{dept},{level}");
}
File.WriteAllText(outputPath, sb.ToString(), Encoding.UTF8);
TaskDialog.Show("Export Complete",
$"{collector.Count()} rooms exported to:\n{outputPath}");Full CSV export — add error handling around File.WriteAllText for production use
Writing to a SQL Database
For more robust data pipelines, write directly to SQL Server or SQLite using standard ADO.NET:
using System.Data.SqlClient;
string connStr = "Server=your-server;Database=BIMData;Trusted_Connection=True;";
using (var conn = new SqlConnection(connStr))
{
conn.Open();
foreach (Element room in collector)
{
string number = room.get_Parameter(
BuiltInParameter.ROOM_NUMBER)?.AsString() ?? "";
double area = UnitUtils.ConvertFromInternalUnits(
room.get_Parameter(BuiltInParameter.ROOM_AREA)
?.AsDouble() ?? 0,
UnitTypeId.SquareMeters);
var cmd = new SqlCommand(
"INSERT INTO Rooms (RoomNumber, Area, ModelFile, ExportDate) " +
"VALUES (@num, @area, @model, GETDATE())", conn);
cmd.Parameters.AddWithValue("@num", number);
cmd.Parameters.AddWithValue("@area", area);
cmd.Parameters.AddWithValue("@model", doc.Title);
cmd.ExecuteNonQuery();
}
}Direct SQL Server write — wrap in try/catch and use upsert logic for production systems
Production Tips
- Use ReadOnly transactions. Data extraction doesn't modify the model, so mark your command with
[Transaction(TransactionMode.ReadOnly)]. This prevents accidental model changes and is faster. - Filter early. Apply as many filters to
FilteredElementCollectoras possible before iterating. Server-side filtering is orders of magnitude faster than client-side iteration. - Handle null parameters gracefully. Always use null-conditional operators (
?.AsString(),?? "") — parameters that don't exist on an element return null, not an exception. - Test on a small model first. Large models with thousands of elements take time to process. Test your query on a stripped-down model before running on a full project model.
Need a custom BIM data pipeline?
Production-grade Revit API tools from $500. Free initial consultation.