You can add your own panels, tabs, buttons, and other controls to Revit menu when creating a Revit plugin. You can customize the controls with images, tooltips, or extended contextual help. However, especially in the new Revit 2025 .NET API, there are some caveats.
📌 TLDR – Summary Upfront:
Modify the Revit menu (e.g., add your own ribbon tabs and panels) using the OnStartup method of your Application class.
➡️
To create a Button, define the PushButtonData with name, text, and linked command.
➡️
To create a TextBox, define the TextBoxData with name.
➡️
To create a ComboBox, define the ComboBoxData with name and populate it with ComboBoxMembers.
You can customize controls with, e.g., tooltips, contextual help, and images (tricky for Revit 2025 API).
To retrieve inserted values, use events: EnterPressed for TextBoxes and CurrentChanged for ComboBoxes.
Where to start – IExternalApplication.OnStartup
To modify the Revit menu, you need the Application plugin type. Implement the IExternalApplication interface and use the OnStartup method to insert your menu-modifying code, such as creating a ribbon tabs and panels.
To get along with the whole plugin creation process, check the Start Revit API 2025 c# Programming Guide.
Example: Creating a ribbon tab called “Revit Mastery” and a 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;
}
}
}
Now, you can add controls to the panel.
Buttons: creating and customizing
Buttons are the most popular and most useful control.
Start by defining a PushButtonData providing 4 attributes:
•
name: the button’s ID. It is not visible in the Revit menu and must be unique. I provide the same as the className.
•
text: the button name displayed in the Revit menu.
•
assemblyName: the location of the currently executing assembly (the same for each control, so you can simply copy it as boilerplate code).
•
className: command to execute when the button is clicked.
C#
[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.
Then, you can add a ToolTip and ContextualHelp (with, e.g., a website link).
Then, you can add a ToolTip and ContextualHelp (with, e.g., a website link).
C#
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);
You can also add a button image, but it requires some preparation.
Images (tricky in Revit 2025)
To add a png/jpg image to controls (Buttons, TextBoxes, ComboBoxes) in Revit 2025, you need the BitmapImage class from System.Windows.Media.Imaging 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:
➡️
Open [YourProjectName].csproj file (Double-click your project name in the Visual Studio Solution Explorer).
➡️
Change <TargetFramework>net8.0</TargetFramework> to <TargetFramework>net8.0-windows</TargetFramework>.
➡️
Add <UseWPF>true</UseWPF>.
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 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. Its constructor requires only one argument: name, which 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, use the EnterPressed event.
The exemplary method attached to the event will retrieve the inserted value, convert it to an integer, and display a TaskDialog. It will also persist the value for further use by 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.
Add ComboBoxData to the panel to create the ComboBox. 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.
You can retrieve the ComboBox value with the CurrentChanged event.
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;
}
}
}