# What Are ONNX Runtime Contrib Operators and How to Register Custom Ones?

> Explore ONNX Runtime contrib operators and learn to register custom ones. Extend ONNX functionality with user-defined kernels using the Ort::CustomOpDomain API.

- Repository: [Microsoft/onnxruntime](https://github.com/microsoft/onnxruntime)
- Tags: how-to-guide
- Published: 2026-04-24

---

**Contrib operators are ONNX Runtime-specific extensions in the Microsoft domain (`kMSDomain`) that extend beyond the ONNX specification, while custom operators are user-defined kernels that you register at runtime via the `Ort::CustomOpDomain` API.**

ONNX Runtime ships with hundreds of built-in operators, but real-world models often require functionality outside the official ONNX specification. In the `microsoft/onnxruntime` repository, **contrib operators** provide Microsoft-supported extensions maintained in the `com.microsoft` domain, while the **custom operator API** allows you to inject your own kernels at runtime without recompiling the framework.

## Understanding Contrib Operators in ONNX Runtime

Contrib operators are implementation-specific extensions that reside in the **Microsoft domain** (`kMSDomain`) rather than the standard ONNX namespace. These operators are maintained by Microsoft and partners to expose bleeding-edge functionality before it becomes part of the official ONNX spec.

According to the source code in [`onnxruntime/core/graph/contrib_ops/contrib_defs.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/graph/contrib_ops/contrib_defs.h), contrib operator schemas are registered globally by the `RegisterContribSchemas()` function implemented in `onnxruntime/core/graph/contrib_ops/contrib_defs.cc`. The concrete kernel implementations are grouped by execution provider in files such as `onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc`, where each provider calls `Register*ContribKernels()` to bind schemas to implementations.

To use a built-in contrib op, reference it in your model with the domain string `com.microsoft` (defined internally as `kMSDomain`). Common examples include `LayerNormalization` and `Range`, which are registered alongside their shape-inference logic in the contrib schema registry.

## How to Register Custom Operators in ONNX Runtime

When you need functionality that contrib operators do not provide, you can register **custom operators** using the C++ or C API. The registration flow follows the same pattern as internal operators but uses the public `OrtCustomOpDomain` APIs instead of the internal registry.

### Step 1: Define the Operator Schema and Kernel

Create a struct that inherits from `Ort::CustomOpBase`. You must implement virtual methods to declare the operator's name, input/output counts, and data types. The test harness in [`onnxruntime/test/shared_lib/custom_op_utils.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/test/shared_lib/custom_op_utils.h) provides the canonical pattern for this.

```cpp
struct MyAddOp : Ort::CustomOpBase<MyAddOp, MyAddKernel> {
  void* CreateKernel(const OrtApi& api, const OrtKernelInfo*) const { 
    return new MyAddKernel(api); 
  }
  
  const char* GetName() const noexcept { return "MyAdd"; }

  size_t GetInputTypeCount() const noexcept { return 2; }
  ONNXTensorElementDataType GetInputType(size_t) const noexcept { 
    return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; 
  }

  size_t GetOutputTypeCount() const noexcept { return 1; }
  ONNXTensorElementDataType GetOutputType(size_t) const noexcept { 
    return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT; 
  }
};

struct MyAddKernel {
  MyAddKernel(const OrtApi& api) : api_(api), ort_(api_) {}
  
  void Compute(OrtKernelContext* context) {
    const OrtValue* a = ort_.KernelContext_GetInput(context, 0);
    const OrtValue* b = ort_.KernelContext_GetInput(context, 1);
    // Perform element-wise addition, allocate output, fill it
  }
  
  const OrtApi& api_;
  Ort::CustomOpApi ort_;
};

```

### Step 2: Create a Custom Domain

Instantiate an `Ort::CustomOpDomain` (or the C API `OrtCustomOpDomain*`) to namespace your operators. This prevents collisions with built-in operators and contrib operators.

```cpp
Ort::CustomOpDomain custom_domain("my.custom.domain");

```

### Step 3: Add the Operator to the Domain

Register your operator instance with the domain using `Add()`. The instance must outlive the session. In the C API, this corresponds to `OrtApis::CustomOpDomain_Add`.

```cpp
MyAddOp my_add_op;
custom_domain.Add(&my_add_op);

```

### Step 4: Attach the Domain to Session Options

Pass the domain to your `OrtSessionOptions` using the `Add` method. The C API equivalent is `OrtApis::AddCustomOpDomain`. When you create the session, ONNX Runtime will resolve any nodes with your custom domain and dispatch them to your kernel implementation.

```cpp
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "example");
Ort::SessionOptions session_options;
session_options.Add(custom_domain);

Ort::Session session(env, "model_with_myadd.onnx", session_options);

```

### Alternative: Register via Shared Library (C API)

For dynamic loading, compile your operators into a shared library that exports a `RegisterCustomOps` function. Use `OrtRegisterCustomOpsLibrary` to load it at runtime, which internally uses `CreateCustomRegistry` from [`onnxruntime/core/session/custom_ops.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/session/custom_ops.h) to bundle the domains.

```c
OrtSessionOptions* opts;
OrtCreateSessionOptions(&opts);

/* Load a shared library that exports:
 *   OrtStatus* RegisterCustomOps(OrtSessionOptions* options, const OrtApiBase* api_base);
 */
OrtRegisterCustomOpsLibrary(opts, "/path/to/my_custom_ops.so", NULL);

OrtSession* sess;
OrtCreateSession(env, "model.onnx", opts, &sess);

```

## Key Implementation Files in ONNX Runtime

The following files in `microsoft/onnxruntime` define the contrib and custom operator infrastructure:

- **[`onnxruntime/core/graph/contrib_ops/contrib_defs.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/graph/contrib_ops/contrib_defs.h)** – Declares `RegisterContribSchemas()` and the contrib operator registry API.
- **`onnxruntime/core/graph/contrib_ops/contrib_defs.cc`** – Implements the registration of built-in contrib schemas such as `LayerNormalization` and `Range`.
- **`onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc`** – Registers CPU kernel implementations for contrib operators via `KernelRegistry::Register`.
- **[`onnxruntime/core/session/custom_ops.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/session/custom_ops.h)** – Contains `CreateCustomRegistry` and the `OrtCustomOpDomain` helpers used by the C API when saving models with custom ops.
- **[`onnxruntime/test/shared_lib/custom_op_utils.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/test/shared_lib/custom_op_utils.h)** – Provides reference implementations like `MyCustomOp` that demonstrate the expected struct pattern for custom op definitions.
- **[`onnxruntime/python/tools/pytorch_export_contrib_ops.py`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/python/tools/pytorch_export_contrib_ops.py)** – Shows how to emit models referencing contrib ops from Python, useful for model conversion pipelines.

## Summary

- **Contrib operators** are Microsoft-maintained extensions in the `com.microsoft` domain (`kMSDomain`), registered via `RegisterContribSchemas()` and implemented per execution provider in files like `cpu_contrib_kernels.cc`.
- **Custom operators** are user-defined kernels that require implementing `Ort::CustomOpBase`, creating an `Ort::CustomOpDomain`, and attaching it to `OrtSessionOptions` before session creation.
- The `CreateCustomRegistry` utility in [`custom_ops.h`](https://github.com/microsoft/onnxruntime/blob/main/custom_ops.h) bundles custom domains for internal use by the Standalone Op Invoker when persisting schemas to ORT format.
- Both registration methods allow ONNX Runtime to execute operators outside the standard ONNX specification, whether built-in or user-defined.

## Frequently Asked Questions

### What is the difference between contrib operators and custom operators?

Contrib operators are built into ONNX Runtime and maintained in the Microsoft domain (`kMSDomain`), with schemas defined in `contrib_defs.cc` and kernels in provider-specific files like `cpu_contrib_kernels.cc`. Custom operators are defined by users at runtime through the `Ort::CustomOpDomain` API without modifying the ONNX Runtime source code, allowing you to extend functionality for specific models.

### How do I load a custom operator library in Python?

Use the `register_custom_ops_library` method on `onnxruntime.SessionOptions` in the Python API. This wraps the underlying `OrtRegisterCustomOpsLibrary` C API call and loads your shared library containing the `RegisterCustomOps` export, making your custom ops available to the inference session.

### Why are contrib operators placed in the com.microsoft domain?

The `com.microsoft` domain (internally `kMSDomain`) isolates Microsoft-specific extensions from the standard ONNX operator set, ensuring that models using these ops are explicitly marked as requiring ONNX Runtime rather than being portable to all ONNX-compliant frameworks. This distinction is enforced by the schema registration logic in `contrib_defs.cc`.

### Can custom operators be used with any execution provider?

Yes. Once registered via `OrtSessionOptions`, custom operators are available to all execution providers in the session. However, your kernel implementation must be compatible with the provider's execution context—CUDA kernels for the CUDA provider, CPU kernels for the CPU provider, etc.—as the runtime dispatches the operator to the appropriate kernel based on the provider configuration.