Creating a Revit plugin allows you to modify the Revit menu by adding your own panels, tabs, buttons, and other controls. You can also customize them with images, tooltips, or extended contextual help. However, especially in the new Revit 2025 .NET API, there are some caveats.
Where to start – IExternalApplication.OnStartup
To modify the Revit menu, you need to create an Application plugin type.
To create the Application plugin, implement the IExternalApplication interface with its OnStartup and OnShutdown methods. The code inside the OnStartup method is executed during Revit’s opening, so this is where to insert the menu-modifying code.
To get along with the whole plugin creation process, check the Start Revit API 2025 c# Programming Guide.
The code below creates a ribbon tab called Revit Mastery and a ribbon panel called Revit Mastery Panel.
C#
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace RevitMastery.Revit
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
public Result OnStartup(UIControlledApplication application)
{
var ribbonTabName = "Revit Mastery";
application.CreateRibbonTab(ribbonTabName);
var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
}
}
Buttons: creating and customizing
Next, we can add controls to our panel. Buttons are the most popular and most useful control.
Start by defining a PushButtonData providing 4 attributes:
So actually, we need two variables to define a button: text to display and the name of a command class to execute on click.
The command class associated with the button must implement the IExternalCommand interface. When the button is clicked, the Execute method will be invoked.
For now, we will just notify the user that the button has been clicked.
C#
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
namespace RevitMastery.Revit
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ShowElementsDataCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message,
ElementSet elements)
{
TaskDialog.Show("Show elements data", "The button has been clicked.");
return Result.Succeeded;
}
}
}
After defining the PushButtonData, add it to a panel to create the actual PushButton.
You can also add a ToolTip and ContextualHelp. The ContextualHelp can, for example, link to a website, which will appear when the user clicks F1 while hovering over the button.
You can also add a ToolTip and ContextualHelp. The ContextualHelp can, for example, link to a website, which will appear when the user clicks F1 while hovering over the button.
C#
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace RevitMastery.Revit
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
public Result OnStartup(UIControlledApplication application)
{
var ribbonTabName = "Revit Mastery";
application.CreateRibbonTab(ribbonTabName);
var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");
var assemblyPath = Assembly.GetExecutingAssembly().Location; //localization of the currently executing assembly. Don't worry, copy ;)
var assemblyDirectory = Path.GetDirectoryName(assemblyPath);
// Button
var buttonData = new PushButtonData(
typeof(ShowElementsDataCommand).FullName,
"Show elements data",
assemblyPath,
typeof(ShowElementsDataCommand).FullName);
var button = panel.AddItem(buttonData) as PushButton;
button.ToolTip = "Click to show data of elements of inserted number and selected type.";
var buttonContextualHelp = new ContextualHelp(ContextualHelpType.Url, "https://revitmastery.com/revit-plugin-types/");
button.SetContextualHelp(buttonContextualHelp);
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
}
}
You can also add a button image, but it requires some preparation.
Images (tricky in Revit 2025)
In Revit 2025’s .NET API, adding images to controls (buttons, textBoxes, comboBoxes) requires some tweaks in the project configuration.
To add a png/jpg image to a control, we need the BitmapImage class from System.Windows.Media.Imaging class namespace. However, this namespace is not available by default in the .NET Class Library project and must be enabled in the project’s .csproj file.
Here’s a step-by-step process:
Finally, the .csproj file code should be similar to:
C#
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="RevitAPI">
<HintPath>..\ExternalLibraries\RevitAPI.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RevitAPIUI">
<HintPath>..\ExternalLibraries\RevitAPIUI.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
</Project>
Now, you can use the System.Windows.Media.Imaging.BitmapImage class to add an image. For a button’s LargeImage, the image size should be 32×32 px.
C#
using System.Windows.Media.Imaging;
// [...] previous code
public Result OnStartup(UIControlledApplication application)
{
// [...] previous code
var buttonImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "description_32.png")); // Image size: 32x32 px
var buttonImage = new BitmapImage(buttonImageUri);
button.LargeImage = buttonImage;
return Result.Succeeded;
}
TextBoxes
TextBoxes allow users to insert text.
Creating a TextBox is similar to creating a button. First, create a TextBoxData object. The TextBoxData constructor requires only one argument: name. This name, like the PushButtonData, serves as the TextBox ID, must be unique, and is not visible in the Revit menu.
Add TextBoxData to the panel to create the TextBox. You can then attach prompt text (hint text shown in an empty TextBox), a tooltip, or an image (size 16×16 px).
C#
public Result OnStartup(UIControlledApplication application)
{
// [...] previous code
// TextBox
var textBoxData = new TextBoxData("nameTextBox");
var textBox = panel.AddItem(textBoxData) as TextBox;
textBox.PromptText = "Number of elements";
textBox.ToolTip = "Enter the number of elements to show and click Enter.";
var textBoxImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "numbers_16.png")); // Image size: 16x16 px
var textBoxImage = new BitmapImage(textBoxImageUri);
textBox.Image = textBoxImage;
textBox.EnterPressed += TextBox_EnterPressed; // event
return Result.Succeeded;
}
Getting TextBoxes values with Events
The purpose of the textbox is to save the inserted value and/or perform some action when the value is inserted. For that, we need to use a textbox event EnterPressed.
Our method attached to the event will retrieve the inserted value and try to convert it to an integer. Then, it will display a TaskDialog informing about the value.
The method will also persist the value for further use by other methods (e.g. when clicking a button). We will do it with a simple approach: using a static property of the Application class.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
public static int SelectedElementsNumber { get; private set; } = 1;
public Result OnStartup(UIControlledApplication application)
{
// [...] previous code
// TextBox
var textBoxData = new TextBoxData("nameTextBox");
var textBox = panel.AddItem(textBoxData) as TextBox;
textBox.PromptText = "Number of elements";
textBox.ToolTip = "Enter the number of elements to show and click Enter.";
var textBoxImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "numbers_16.png")); // Image size: 16x16 px
var textBoxImage = new BitmapImage(textBoxImageUri);
textBox.Image = textBoxImage;
textBox.EnterPressed += TextBox_EnterPressed; // event
return Result.Succeeded;
}
private void TextBox_EnterPressed(object? sender, Autodesk.Revit.UI.Events.TextBoxEnterPressedEventArgs e)
{
var textBox = sender as TextBox;
var textBoxValue = textBox.Value.ToString();
var isTextBoxValueInt = int.TryParse(textBoxValue, out var intTextBoxValue);
if(isTextBoxValueInt)
{
SelectedElementsNumber = intTextBoxValue; // assigning to a static property
TaskDialog.Show("TextBox", $"Current texBox value is: {textBoxValue}");
}
else
{
TaskDialog.Show("TextBox", $"Insert an integer!");
}
}
}
ComboBoxes
ComboBoxes allow users to select from predefined values.
Creating a ComboBox is similar to creating a TextBox. Add ComboBoxData to the panel to create the ComboBox. Additionally, the ComboBox must be populated with ComboBoxMembers. Each ComboBoxMemberData is defined by a name (ID-like, not visible in Revit, and unique) and the text shown in the Revit menu.
Similar to a TextBox, you can retrieve the ComboBox value with an event – in this case, CurrentChanged.
Our method attached to this event will display a TaskDialog with the current value, determine the element type, and save the type to a static property of the Application class for later use.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
public static Type SelectedElementType { get; private set; } = typeof(Wall);
public Result OnStartup(UIControlledApplication application)
{
// [...] previous code
// ComboBox
var comboBoxData = new ComboBoxData("elementTypeComboBox");
var comboBox = panel.AddItem(comboBoxData) as ComboBox;
comboBox.ToolTip = "Select elements type.";
var comboBoxImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "list_16.png")); // Image size: 16x16 px
var comboBoxImage = new BitmapImage(comboBoxImageUri);
comboBox.Image = comboBoxImage;
var comboBoxMemberData_wall = new ComboBoxMemberData("elementTypeComboBox_wall", "Walls");
var comboBoxMember_wall = comboBox.AddItem(comboBoxMemberData_wall);
var comboBoxMemberData_floor = new ComboBoxMemberData("elementTypeComboBox_floor", "Floors");
var comboBoxMember_floor = comboBox.AddItem(comboBoxMemberData_floor);
comboBox.CurrentChanged += ComboBox_CurrentChanged; //event
return Result.Succeeded;
}
private void ComboBox_CurrentChanged(object? sender, Autodesk.Revit.UI.Events.ComboBoxCurrentChangedEventArgs e)
{
var comboBox = sender as ComboBox;
var comboBoxValue = comboBox.Current.ItemText;
var elementType = comboBoxValue == "Walls" ? typeof(Wall) : typeof(Floor);
SelectedElementType = elementType; // assigning to a static property
TaskDialog.Show("ComboBox", $"Current comboBox value is: {comboBoxValue}");
}
}
Putting it all into action – Command
To bring everything together, let’s modify the Command class to utilize the persisted values from the TextBox and the ComboBox.
The command method will show a TaskDialog with names of elements of the selected type (walls or floors) existing in the project. The number of names displayed will be limited by the number inserted in the TextBox (or the number of existing elements, if smaller).
C#
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
namespace RevitMastery.Revit
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ShowElementsDataCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message,
ElementSet elements)
{
UIApplication uiApp = commandData.Application;
Document doc = uiApp.ActiveUIDocument.Document;
var elementsNumber = RevitMasteryApplication.SelectedElementsNumber;
var elementsType = RevitMasteryApplication.SelectedElementType;
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElements = collector.ToElements();
var numberToShow = Math.Min(collectedElements.Count, elementsNumber);
var elementsMessage = "Elements names: \n";
for ( var i = 0; i<numberToShow; i++)
{
var element = collectedElements[i];
var elementName = element.Name;
elementsMessage += $"{i+1}. {elementName}\n";
}
TaskDialog.Show("Show elements data", elementsMessage);
return Result.Succeeded;
}
}
}
The Final Code
C#
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace RevitMastery.Revit
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
public static int SelectedElementsNumber { get; private set; } = 1;
public static Type SelectedElementType { get; private set; } = typeof(Wall);
public Result OnStartup(UIControlledApplication application)
{
var ribbonTabName = "Revit Mastery";
application.CreateRibbonTab(ribbonTabName);
var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");
var assemblyPath = Assembly.GetExecutingAssembly().Location; //localization of the currently executing assembly. Don't worry, copy ;)
var assemblyDirectory = Path.GetDirectoryName(assemblyPath);
// Button
var buttonData = new PushButtonData(
typeof(ShowElementsDataCommand).FullName,
"Show elements data",
assemblyPath,
typeof(ShowElementsDataCommand).FullName);
var button = panel.AddItem(buttonData) as PushButton;
button.ToolTip = "Click to show data of elements of inserted number and selected type.";
var buttonContextualHelp = new ContextualHelp(ContextualHelpType.Url, "https://revitmastery.com/revit-plugin-types/");
button.SetContextualHelp(buttonContextualHelp);
var buttonImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "description_32.png")); // Image size: 32x32 px
var buttonImage = new BitmapImage(buttonImageUri);
button.LargeImage = buttonImage;
// TextBox
var textBoxData = new TextBoxData("nameTextBox");
var textBox = panel.AddItem(textBoxData) as TextBox;
textBox.PromptText = "Number of elements";
textBox.ToolTip = "Enter the number of elements to show and click Enter.";
var textBoxImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "numbers_16.png")); // Image size: 16x16 px
var textBoxImage = new BitmapImage(textBoxImageUri);
textBox.Image = textBoxImage;
textBox.EnterPressed += TextBox_EnterPressed; // event
// ComboBox
var comboBoxData = new ComboBoxData("elementTypeComboBox");
var comboBox = panel.AddItem(comboBoxData) as ComboBox;
comboBox.ToolTip = "Select elements type.";
var comboBoxImageUri = new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "list_16.png")); // Image size: 16x16 px
var comboBoxImage = new BitmapImage(comboBoxImageUri);
comboBox.Image = comboBoxImage;
var comboBoxMemberData_wall = new ComboBoxMemberData("elementTypeComboBox_wall", "Walls");
var comboBoxMember_wall = comboBox.AddItem(comboBoxMemberData_wall);
var comboBoxMemberData_floor = new ComboBoxMemberData("elementTypeComboBox_floor", "Floors");
var comboBoxMember_floor = comboBox.AddItem(comboBoxMemberData_floor);
comboBox.CurrentChanged += ComboBox_CurrentChanged; //event
return Result.Succeeded;
}
private void TextBox_EnterPressed(object? sender, Autodesk.Revit.UI.Events.TextBoxEnterPressedEventArgs e)
{
var textBox = sender as TextBox;
var textBoxValue = textBox.Value.ToString();
var isTextBoxValueInt = int.TryParse(textBoxValue, out var intTextBoxValue);
if(isTextBoxValueInt)
{
SelectedElementsNumber = intTextBoxValue; // assigning to a static property
TaskDialog.Show("TextBox", $"Current texBox value is: {textBoxValue}");
}
else
{
TaskDialog.Show("TextBox", $"Insert an integer!");
}
}
private void ComboBox_CurrentChanged(object? sender, Autodesk.Revit.UI.Events.ComboBoxCurrentChangedEventArgs e)
{
var comboBox = sender as ComboBox;
var comboBoxValue = comboBox.Current.ItemText;
var elementType = comboBoxValue == "Walls" ? typeof(Wall) : typeof(Floor);
SelectedElementType = elementType; // assigning to a static property
TaskDialog.Show("ComboBox", $"Current comboBox value is: {comboBoxValue}");
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
}
}
C#
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
namespace RevitMastery.Revit
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ShowElementsDataCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message,
ElementSet elements)
{
UIApplication uiApp = commandData.Application;
Document doc = uiApp.ActiveUIDocument.Document;
var elementsNumber = RevitMasteryApplication.SelectedElementsNumber;
var elementsType = RevitMasteryApplication.SelectedElementType;
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElements = collector.ToElements();
var numberToShow = Math.Min(collectedElements.Count, elementsNumber);
var elementsMessage = "Elements names: \n";
for ( var i = 0; i<numberToShow; i++)
{
var element = collectedElements[i];
var elementName = element.Name;
elementsMessage += $"{i+1}. {elementName}\n";
}
TaskDialog.Show("Show elements data", elementsMessage);
return Result.Succeeded;
}
}
}
Summary
Creating Revit menu tabs, panels, and controls is one of the first steps toward developing powerful plugins for you and your clients. Customizing the controls adds a “styling touch.” While buttons are the most popular control types, TextBoxes and ComboBoxes are also useful to save values and extend plugins’ capabilities.
Do you want to become the Revit Programmer of the Future?