# Revit External Events

> Run Autodesk Revit API work from code that executes outside the Revit API context, using Nice3point.Revit.Toolkit external events. USE FOR: raising API work from a modeless window, background thread, or other non-API-context callback via the toolkit's external events or the [ExternalEvent] source generator. DO NOT USE FOR: code that already runs in the Revit API context — call the API directly.

- Skill: `nice3point/revit-external-events` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nice3point/revit-external-events`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nice3point/revit-external-events/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: nice3point (https://skillmd.com/u/nice3point)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nice3point/revit-external-events

---


# Revit External Events

The Revit API may only be touched inside its API context.
`Nice3point.Revit.Toolkit` external events auto-initialize; a caller constructs an event and raises it from any thread without creating it inside the API context first.

## When to use

- Invoking the API from a modeless WPF window, a background task, or another non-API-context callback.
- Reviewing that code outside the API context does not touch the API directly.

## When not to use

- The caller already runs in the API context (an external command, a Revit event handler, a Revit-invoked callback) — call the API directly.
- The window is shown modally with `ShowDialog`: it runs on the API thread, where the API is reachable directly.

## Workflow

### Step 1: Choose the event shape

- `ExternalEvent` / `ExternalEvent<T>` — fire-and-forget with zero or one argument.
- `AsyncExternalEvent` / `AsyncExternalEvent<T>` — await completion.
- `AsyncRequestExternalEvent<TResult>` / `<T, TResult>` — await a returned result.

### Step 2: Prefer the [ExternalEvent] generator

Mark every containing type `partial` and annotate a handler method with `[ExternalEvent]`.
The generator emits the event member from the method signature and names it after the method — `<Method>Event` for the synchronous form and `<Method>AsyncEvent` for the awaitable one — and the caller raises the work through that generated property.
Keep the transaction inside the method.

```csharp
public sealed partial class WindowCleaner
{
    [ExternalEvent]
    private void DeleteWindows(UIApplication application)
    {
        var document = application.ActiveUIDocument.Document;
        using var transaction = new Transaction(document, "Delete windows");
        transaction.Start();
        document.Delete(document.GetInstanceIds(BuiltInCategory.OST_Windows));
        transaction.Commit();
    }

    // from any thread or context
    private void OnDeleteClick() => DeleteWindowsEvent.Raise();
}
```

A value-returning method generates only the awaitable request form (`<Method>AsyncEvent`, raised with `await`); a method with two or more extra parameters also gets a generated `Raise(...)`/`RaiseAsync(...)` taking the individual arguments.

### Step 3: Construct an event manually when the generator does not fit

Use the public constructors for a lambda or field, a non-`partial` type, a DI-created instance, or a signature the generator rejects.
Each event type offers two handler families — one that takes the argument(s) only, and one that also receives the current `UIApplication` — and every family has an optional `ExternalEventOptions` overload.
(There is no `ExternalEvent.Create(...)` factory; the constructor is the manual entry point.)

```csharp
private readonly ExternalEvent<ElementId> _deleteElement = new((application, elementId) =>
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete element");
    transaction.Start();
    document.Delete(elementId);
    transaction.Commit();
});

// from any thread or context
_deleteElement.Raise(elementId);
```

Omit `UIApplication` where the handler does not use it (`new ExternalEvent(() => …)`, `new ExternalEvent<T>(arg => …)`) and read ambient state through `RevitContext`.
Await completion with `AsyncExternalEvent`/`AsyncExternalEvent<T>` (`await …RaiseAsync()`), and return a value with `AsyncRequestExternalEvent<TResult>`/`<T, TResult>` (`var result = await …RaiseAsync(arg)`).

### Step 4: Allow direct invocation only for dual-context operations

Set `[ExternalEvent(AllowDirectInvocation = true)]` only when one operation must serve callers inside and outside the API context; it runs inline when already in API mode.
Never use it to bypass ownership or concurrency rules.

### Step 5: Verify

Await async event results before consuming generated Revit objects, and test the observable model result.

## Validation

- [ ] Code outside the API context reaches the API through an external event.
- [ ] Transactions stay inside the event callback or annotated method.
- [ ] An `[ExternalEvent]` method's containing type is `partial`.
- [ ] `AllowDirectInvocation` is set only for a real dual-context need.

## Common Pitfalls

| Pitfall                                                                     | Correct approach                                                |
|-----------------------------------------------------------------------------|-----------------------------------------------------------------|
| `[ExternalEvent]` on a method whose type is not `partial` (RVTTK0005)       | Mark the containing type `partial`.                             |
| `[ExternalEvent]` method returns `Task` or is `async void` (RVTTK0001/0002) | Use a synchronous signature; use `Async…` event types to await. |
| Generic `[ExternalEvent]` method (RVTTK0003)                                | Use concrete parameter types.                                   |
| Using an external event while already in API context                        | Call the API directly.                                          |
| `ExternalEvent` or `[ExternalEvent]` not found                              | The `Nice3point.Revit.Toolkit` package is not referenced.       |

