tutorials

Building Your First Revit Add-in: Step-by-Step Tutorial

Complete walkthrough of creating a Revit add-in from scratch, including project setup, commands, and deployment.

Ahmed Sherif
December 14, 2025
revit,addin,tutorial,beginner

Building Your First Revit Add-in: Step-by-Step Tutorial

Creating your first Revit add-in represents an exciting milestone in BIM development, transforming you from a Revit user into a Revit developer capable of extending the software's capabilities. This comprehensive tutorial guides you through the complete process of building a functional add-in, from setting up your development environment to deploying your finished product. By the end, you'll have created a working add-in that demonstrates fundamental Revit API concepts and provides a foundation for more complex projects.

Understanding Revit Add-in Architecture

Revit add-ins are .NET assemblies (DLL files) that Revit loads at startup or on demand. These assemblies contain classes that implement specific Revit API interfaces, enabling your code to respond to user actions and interact with the Revit document. The two primary add-in types are External Commands, which execute when users click buttons or menu items, and External Applications, which load when Revit starts and can register for events or create custom user interfaces.

External Commands represent the simplest add-in type, perfect for beginners. They implement the IExternalCommand interface, which requires a single Execute method that runs when the command is invoked. This tutorial focuses on creating an External Command that demonstrates core Revit API concepts while solving a practical problem.

Setting Up Your Development Environment

Before writing code, you need a properly configured development environment. Install Visual Studio Community Edition (free) or any other edition, ensuring you select the .NET desktop development workload during installation. Revit 2024 uses .NET Framework 4.8, so verify this framework is installed on your system.

Create a new Class Library project targeting .NET Framework 4.8. Name your project "MyFirstRevitAddin" and choose a location for your project files. Once created, add references to the Revit API assemblies by right-clicking References in Solution Explorer and selecting Add Reference. Browse to your Revit installation directory (typically C:\Program Files\Autodesk\Revit 2024) and add references to:

  • RevitAPI.dll
  • RevitAPIUI.dll

Set "Copy Local" to False for both references, as these assemblies should not be copied to your output directory.

Creating the Add-in Manifest

Revit discovers add-ins through manifest files with the .addin extension. Create a new text file named MyFirstRevitAddin.addin in your project directory with the following content:

xml
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
  <AddIn Type="Command">
    <Assembly>MyFirstRevitAddin.dll</Assembly>
    <ClientId>12345678-1234-1234-1234-123456789012</ClientId>
    <FullClassName>MyFirstRevitAddin.Command</FullClassName>
    <Text>My First Command</Text>
    <Description>A simple command that demonstrates Revit API basics</Description>
    <VisibilityMode>AlwaysVisible</VisibilityMode>
    <VendorId>MYCO</VendorId>
    <VendorDescription>My Company</VendorDescription>
  </AddIn>
</RevitAddIns>

Generate a unique GUID for the ClientId using Visual Studio's Tools > Create GUID menu or an online GUID generator. The FullClassName must match your class's namespace and name exactly, or Revit won't be able to load your add-in.

Writing Your First Command

Now create the actual command class that implements IExternalCommand. This example creates a command that counts walls in the current document and displays the result to the user.

csharp
using System;
using System.Linq;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace MyFirstRevitAddin
{
    [Transaction(TransactionMode.ReadOnly)]
    public class Command : IExternalCommand
    {
        public Result Execute(
            ExternalCommandData commandData,
            ref string message,
            ElementSet elements)
        {
            // Get the current Revit document
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            Document doc = uidoc.Document;
            
            try
            {
                // Count walls in the document
                FilteredElementCollector collector = new FilteredElementCollector(doc);
                collector.OfClass(typeof(Wall));
                collector.WhereElementIsNotElementType();
                
                int wallCount = collector.Count();
                
                // Display result to user
                TaskDialog.Show(
                    "Wall Counter",
                    $"This project contains {wallCount} walls.");
                
                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                message = ex.Message;
                return Result.Failed;
            }
        }
    }
}

The Transaction attribute specifies that this command only reads data and doesn't modify the document. The Execute method receives an ExternalCommandData parameter that provides access to the Revit application and document. The method must return a Result enum value indicating success, failure, or cancellation.

Understanding the Code Structure

Let's examine each part of this command in detail. The ExternalCommandData parameter provides your entry point into the Revit API through its Application property, which gives access to the UIApplication. From there, you can get the active UIDocument and its underlying Document.

The FilteredElementCollector is your primary tool for querying elements in the Revit database. It uses a fluent interface where you chain filter methods to narrow down the elements you want. The OfClass filter restricts results to a specific element class, while WhereElementIsNotElementType excludes element types (definitions) and returns only placed instances.

The TaskDialog class provides a simple way to display messages to users. It's more flexible than standard Windows message boxes and integrates better with Revit's user interface. For more complex dialogs, you can create custom WPF windows, but TaskDialog suffices for simple messages and confirmations.

Building and Deploying Your Add-in

Build your project in Visual Studio by pressing F6 or selecting Build > Build Solution. If successful, Visual Studio creates MyFirstRevitAddin.dll in your project's bin\Debug or bin\Release folder.

To deploy your add-in, copy both the DLL file and the .addin manifest to one of Revit's add-in folders:

  • User-specific: C:\Users\[Username]\AppData\Roaming\Autodesk\Revit\Addins\2024
  • All users: C:\ProgramData\Autodesk\Revit\Addins\2024

The user-specific folder is recommended during development, as it doesn't require administrator privileges and won't affect other users.

Testing Your Add-in

Launch Revit and create or open a project containing some walls. Your add-in should appear in the Add-Ins tab of the ribbon under External Tools. Click your command button, and you should see a dialog displaying the wall count.

If your command doesn't appear, check the following:

  • Verify the .addin file is in the correct folder
  • Ensure the ClientId is a valid GUID
  • Confirm the FullClassName matches your actual namespace and class name
  • Check that the Assembly name matches your DLL filename
  • Review Revit's journal file for error messages (located in the same folder as your project files)

Enhancing Your Add-in

Now that you have a working add-in, let's enhance it with additional functionality. This improved version allows users to select walls and displays detailed information about them.

csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;

namespace MyFirstRevitAddin
{
    [Transaction(TransactionMode.ReadOnly)]
    public class WallInfoCommand : IExternalCommand
    {
        public Result Execute(
            ExternalCommandData commandData,
            ref string message,
            ElementSet elements)
        {
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            Document doc = uidoc.Document;
            
            try
            {
                // Prompt user to select walls
                IList<Reference> selectedRefs = uidoc.Selection.PickObjects(
                    ObjectType.Element,
                    new WallSelectionFilter(),
                    "Select walls to analyze");
                
                if (selectedRefs.Count == 0)
                {
                    TaskDialog.Show("Wall Info", "No walls selected.");
                    return Result.Cancelled;
                }
                
                // Analyze selected walls
                StringBuilder report = new StringBuilder();
                report.AppendLine($"Analyzed {selectedRefs.Count} walls:\n");
                
                double totalArea = 0;
                double totalVolume = 0;
                
                foreach (Reference wallRef in selectedRefs)
                {
                    Wall wall = doc.GetElement(wallRef) as Wall;
                    
                    // Get wall properties
                    Parameter areaParam = wall.get_Parameter(BuiltInParameter.HOST_AREA_COMPUTED);
                    Parameter volumeParam = wall.get_Parameter(BuiltInParameter.HOST_VOLUME_COMPUTED);
                    
                    double area = areaParam.AsDouble();
                    double volume = volumeParam.AsDouble();
                    
                    totalArea += area;
                    totalVolume += volume;
                    
                    report.AppendLine($"Wall ID {wall.Id.IntegerValue}:");
                    report.AppendLine($"  Type: {wall.WallType.Name}");
                    report.AppendLine($"  Area: {UnitFormatUtils.Format(doc.GetUnits(), SpecTypeId.Area, area, false)}");
                    report.AppendLine($"  Volume: {UnitFormatUtils.Format(doc.GetUnits(), SpecTypeId.Volume, volume, false)}");
                    report.AppendLine();
                }
                
                report.AppendLine("Totals:");
                report.AppendLine($"  Total Area: {UnitFormatUtils.Format(doc.GetUnits(), SpecTypeId.Area, totalArea, false)}");
                report.AppendLine($"  Total Volume: {UnitFormatUtils.Format(doc.GetUnits(), SpecTypeId.Volume, totalVolume, false)}");
                
                // Display report
                TaskDialog dialog = new TaskDialog("Wall Analysis");
                dialog.MainContent = report.ToString();
                dialog.Show();
                
                return Result.Succeeded;
            }
            catch (Autodesk.Revit.Exceptions.OperationCanceledException)
            {
                return Result.Cancelled;
            }
            catch (Exception ex)
            {
                message = ex.Message;
                return Result.Failed;
            }
        }
    }
    
    public class WallSelectionFilter : ISelectionFilter
    {
        public bool AllowElement(Element elem)
        {
            return elem is Wall;
        }
        
        public bool AllowReference(Reference reference, XYZ position)
        {
            return false;
        }
    }
}

This enhanced version demonstrates several important concepts:

Selection filtering through the ISelectionFilter interface ensures users can only select walls, providing a better user experience than allowing any element and then validating the selection.

Parameter access shows how to read built-in parameters using the BuiltInParameter enumeration. The AsDouble method retrieves numeric values in Revit's internal units (square feet for area, cubic feet for volume).

Unit formatting through UnitFormatUtils.Format converts internal units to the document's display units, ensuring values appear in the format users expect (meters, millimeters, etc.).

Adding a Ribbon Panel

Professional add-ins typically create custom ribbon panels rather than appearing under External Tools. Create an External Application class to register your commands in a custom panel:

csharp
using System;
using System.Reflection;
using Autodesk.Revit.UI;

namespace MyFirstRevitAddin
{
    public class Application : IExternalApplication
    {
        public Result OnStartup(UIControlledApplication application)
        {
            try
            {
                // Create ribbon panel
                RibbonPanel panel = application.CreateRibbonPanel("My Tools");
                
                // Get assembly path
                string assemblyPath = Assembly.GetExecutingAssembly().Location;
                
                // Create push button for wall counter
                PushButtonData buttonData = new PushButtonData(
                    "WallCounter",
                    "Count Walls",
                    assemblyPath,
                    "MyFirstRevitAddin.Command");
                
                PushButton button = panel.AddItem(buttonData) as PushButton;
                button.ToolTip = "Count walls in the current project";
                
                // Create push button for wall info
                PushButtonData infoButtonData = new PushButtonData(
                    "WallInfo",
                    "Wall Info",
                    assemblyPath,
                    "MyFirstRevitAddin.WallInfoCommand");
                
                PushButton infoButton = panel.AddItem(infoButtonData) as PushButton;
                infoButton.ToolTip = "Analyze selected walls";
                
                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                TaskDialog.Show("Error", ex.Message);
                return Result.Failed;
            }
        }
        
        public Result OnShutdown(UIControlledApplication application)
        {
            return Result.Succeeded;
        }
    }
}

Update your .addin manifest to register the External Application:

xml
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
  <AddIn Type="Application">
    <Assembly>MyFirstRevitAddin.dll</Assembly>
    <ClientId>12345678-1234-1234-1234-123456789012</ClientId>
    <FullClassName>MyFirstRevitAddin.Application</FullClassName>
    <VendorId>MYCO</VendorId>
    <VendorDescription>My Company</VendorDescription>
  </AddIn>
</RevitAddIns>

Now your commands appear in a custom "My Tools" ribbon panel, providing a more professional appearance and better organization.

Best Practices for Add-in Development

As you continue developing Revit add-ins, follow these best practices to create robust, maintainable code:

Error handling should be comprehensive, catching specific exceptions where possible and providing meaningful error messages to users. Always return appropriate Result values from your Execute methods.

Transaction management requires careful attention. Use the Transaction attribute to declare your command's transaction requirements, and create explicit transactions when modifying the document.

Performance optimization matters, especially in large models. Use filtered element collectors efficiently, minimize transaction count, and cache frequently accessed data.

User feedback improves the experience significantly. Show progress indicators for long-running operations, validate user input before processing, and provide clear success and error messages.

Conclusion

You've now created your first Revit add-in, learning fundamental concepts that form the foundation for more advanced development. This tutorial covered setting up your development environment, creating External Commands, using the FilteredElementCollector, accessing parameters, handling user selection, and creating custom ribbon panels.

The skills you've learned enable you to automate repetitive tasks, implement custom analysis tools, and extend Revit's capabilities to meet specific project requirements. As you continue your Revit API journey, study the official API documentation, examine open-source add-ins, and participate in the Autodesk developer forums.

Start by identifying repetitive tasks in your daily work that could benefit from automation, then build simple add-ins to address those needs. With each project, you'll gain confidence and discover new API capabilities that enable increasingly sophisticated solutions. The investment in learning Revit add-in development pays dividends through improved productivity, reduced errors, and the ability to customize Revit to your exact requirements.

Need Custom BIM Solutions?

Let's discuss how we can help optimize your BIM workflow.