Skip to main content
Back to Knowledge Hub
AI & Automation
7 min read

Enterprise-Scalable pyRevit: Pairing C# Add-ins with the Invoke Pattern

Ali Tehami· Co-founder, GIRIH XPublished 14 May 2026
TL;DR

pyRevit's invoke pattern lets a full Visual Studio C# add-in run from inside a pyRevit button, paired with pyRevit's UX and deployment layer. The result is a development model that combines the rapid prototyping and ribbon ergonomics of pyRevit with the IDE, debugging, performance and library ecosystem of a proper .NET add-in. Ali Tehami, co-founder of GIRIH X, contributed a multi-version reference implementation back to the pyRevit community in 2023.

What pyRevit's invoke pattern actually is

pyRevit ships an invoke bundle type that lets a button execute a method on a compiled .NET assembly rather than a Python script. From the team's perspective, the button still lives in pyRevit's familiar bundle structure: a folder, a script, an icon, a bundle definition. From the developer's perspective, the actual command runs inside a Visual Studio C# project that builds against the Revit API directly. pyRevit handles the ribbon, the loading, the user-facing surface; Visual Studio handles the engineering.

Why pair pyRevit with a C# add-in at all

Pure Python scripts in pyRevit are excellent for fast wins, ad-hoc tools and team-wide quick utilities, but they hit ceilings: limited debugging in IronPython, slower performance on heavy element traversals, weaker support for the wider .NET library ecosystem, and a less predictable engineering workflow once tools grow beyond a single file. A C# add-in solves all of those: a real IDE, attach-to-process debugging, NuGet packages, full IntelliSense, unit tests, the lot. The trade-off has historically been pyRevit's ribbon ergonomics, deployment, and team distribution model. The invoke pattern keeps both sides.

The development model in practice

In production we run a single pyRevit extension with a mix of bundle types. Quick utilities stay as pure Python scripts. Anything that needs to scale, hold logic across many calls, integrate with external services, or carry meaningful state moves into a Visual Studio C# solution that compiles a DLL into the extension's lib folder. The pyRevit invoke button calls into that DLL. From the user's perspective there is one ribbon, one extension, one update path. From the developer's perspective the heavy logic lives in a properly engineered .NET project.

Multi-version Revit support without rewrites

The hard part with C# add-ins is the Revit API: API surfaces change between versions, and a single DLL targeting one Revit version cannot be loaded into another safely. The pattern Ali Tehami contributed back to the pyRevit community in 2023 solves this by keeping a main library project that holds the executing IExternalCommand classes, plus thin version-specific helper libraries that each reference one Revit API version. The main library calls the helpers via dynamic types and runtime version checks. Costura.Fody embeds the version-specific helpers as resources in the main DLL so there is one artefact to deploy, not several.

Visual Studio configuration that makes it routine

Two Visual Studio configurations carry most of the load. First, the project conditionally switches references to the Revit API and Revit UI assemblies based on the build configuration (debug_2018, debug_2019, debug_2020, and so on), so the same source can be tested against any supported Revit version without project surgery. Second, post-build events copy the compiled assembly straight into the pyRevit extension's lib folder so the next Revit launch picks up the new build with no manual file shuffling. The combination is what turns a clever pattern into a daily-driver workflow.

Rapid prototyping and testing without the friction

Once the pattern is in place, prototyping a new tool looks like this: open Visual Studio, scaffold a new IExternalCommand, write the logic with full IntelliSense and debugger support, attach to the running Revit process, set breakpoints, iterate. The pyRevit button stays the same throughout; the user-facing entry point does not change while the implementation is being engineered. New tools can land on the ribbon within hours, not days, and they ship with the same governance, deployment and update mechanisms as every other pyRevit bundle.

How this fits a serious BIM operating model

The invoke pattern slots cleanly into the wider design and delivery automation library described elsewhere in this hub. Quick wins enter as Python or Dynamo. Tools that prove valuable and need to scale graduate into the Visual Studio C# project, behind a stable invoke button. The team's standards, naming and documentation conventions apply across the whole library; the user does not need to know whether they are clicking a Python script or a compiled add-in. That continuity is what lets a digital team support a growing toolset without it fragmenting into incompatible delivery surfaces.

Why GIRIH X uses this pattern in production

GIRIH X builds bespoke Revit automation for clients across architecture, healthcare PPP delivery and product manufacturing. The invoke pattern is the default development model for anything that goes beyond a small Python script: it gives the team a proper engineering surface, the client a single coherent ribbon, and the pyRevit ecosystem a familiar deployment and update path. The pattern itself is open and documented in the public proof-of-concept repository so other teams can adopt and extend it directly.

Frequently asked questions

What is a pyRevit invoke button?

A pyRevit bundle type that executes a method on a compiled .NET assembly instead of running a Python script. The button lives inside pyRevit's normal extension structure, but the underlying command is implemented in a Visual Studio C# project that targets the Revit API directly.

Why use C# instead of staying in pure pyRevit Python?

Python in pyRevit is excellent for fast wins and quick utilities. C# is the better fit when a tool needs proper IDE support, attach-to-process debugging, the wider .NET library ecosystem, performance on heavy element traversals, or when it needs to grow into a maintainable, multi-developer project.

How do you support multiple Revit versions from one C# project?

Keep one main library that holds the executing IExternalCommand classes, plus small version-specific helper libraries that each reference one Revit API version. Call the helpers from the main library via dynamic types and runtime version checks. Embed the helpers as resources using Costura.Fody so there is a single deployable DLL.

Did Ali Tehami invent the pyRevit invoke button itself?

No. The invoke bundle type is part of the pyRevit project. The contribution Ali made to the community in 2023 was a documented pattern and a public reference implementation showing how to build a multi-version C# add-in behind an invoke button, including the Visual Studio configuration and the Costura.Fody embedding approach. The discussion is preserved in the pyRevit Discourse thread linked below.

Where can I see the reference implementation?

The public proof-of-concept repository is on GitHub at alitehami/pyRevitBetaIdeas_Public. It contains the invoke.pushbutton boilerplate, the multi-version helper library setup, the Visual Studio post-build copy configuration, and the conditional Revit API references.

Does this replace ISO 19650 or BIM Execution Plan governance?

No. The invoke pattern is a development and deployment model for Revit tooling. The governance of how those tools are used on a project, who owns them, how they version, and how they integrate with the CDE workflow, still belongs in the BIM Execution Plan and the team's standards.

Need help implementing this in your projects?

We build production-grade systems, not theoretical frameworks. Let's discuss your specific challenges.