CLI extension templates#
The command-line interface (CLI) extension templates allow you to scaffold new Isaac Sim extensions from the terminal using ./repo.sh template new. Four templates cover the most common extension patterns:
Python extension — Minimal Python-only extension with
extension.py, docs, and test scaffolding.UI extension — Python extension with Examples Browser integration, Load World/Reset controls, physics callbacks, and custom UI using the experimental
BaseSampleandBaseSampleUITemplateclasses.C++ extension — Extension with a Carbonite C++ plugin, pybind11 Python bindings, and a Python wrapper.
OmniGraph extension — Extension with C++ and Python OmniGraph Node (OGN) definitions, a Carbonite plugin, pybind11 bindings, and OGN build integration.
The generator places all extensions in source/extensions/, and the build system discovers them automatically — no manual registration required.
Prerequisites#
The template generator runs from the Isaac Sim source tree. If you have not already cloned the open-source repository, do so first:
git clone -b main https://github.com/isaac-sim/IsaacSim.git isaacsim
cd isaacsim
All commands shown in this guide are run from the repository root (the directory that contains
repo.sh, build.sh, and templates/). On Windows, substitute ./repo.sh with .\repo.bat
and ./build.sh with .\build.bat.
The first invocation of ./build.sh prompts you to accept the NVIDIA Software License Agreement.
After acceptance, a marker file .eula_accepted is written to the repository root and the prompt does
not appear again. Similarly, ./repo.sh template new has its own EULA prompt that writes
.omniverse_eula_accepted.txt. Both files must exist for a fully non-interactive workflow.
See Non-interactive usage (CI) below for how to pre-accept the EULA in non-interactive
(CI) environments.
For the full list of platform requirements (OS, GPU, driver, build tools), see the project README.
Getting started#
To create a new extension:
./repo.sh template new
Follow the interactive prompts to select a template and configure your extension name, title, and other variables.
Available templates#
Python extension#
A minimal Python-only extension. Use this when you need a lightweight extension without UI, scene management, or C++ code.
Generated structure:
source/extensions/isaacsim.my.extension/
├── config/extension.toml
├── data/icon.png, preview.png
├── docs/Overview.md, CHANGELOG.md
├── isaacsim/my/extension/
│ ├── __init__.py
│ ├── extension.py
│ └── tests/__init__.py, test_extension.py
└── premake5.lua
Key files to modify:
extension.py— Add your startup/shutdown logic inon_startup()andon_shutdown().
UI extension#
A Python extension with full UI and scene management. This template provides:
Examples Browser integration — Your extension appears under your chosen category in Window > Examples Browser.
Load/Reset world controls — Pre-built Load World and Reset buttons that manage the simulation lifecycle.
Physics callbacks — A
scenario.pywithon_physics_step()called every simulation step viaSimulationManager.Custom UI — A
ui.pywith an Actions frame where you add your own buttons and controls usingbtn_builderand otherUIElementWrapperutilities.
The template uses the experimental APIs:
isaacsim.examples.base.base_sample_experimental.BaseSamplefor scene lifecycleisaacsim.examples.base.base_sample_extension_experimental.BaseSampleUITemplatefor UIisaacsim.core.simulation_manager.SimulationManagerfor physics callbacksisaacsim.core.experimental.utilsfor stage and app utilities
Generated structure:
source/extensions/isaacsim.my.example/
├── config/extension.toml
├── data/icon.png, preview.png
├── docs/Overview.md, CHANGELOG.md
├── isaacsim/my/example/
│ ├── __init__.py
│ ├── extension.py ← Examples Browser registration
│ ├── scenario.py ← BaseSample with physics callbacks
│ ├── ui.py ← BaseSampleUITemplate with custom controls
│ └── tests/__init__.py, test_extension.py
└── premake5.lua
Key files to modify:
scenario.py— Add your simulation assets insetup_scene()and logic inon_physics_step().ui.py— Add custom buttons and controls inbuild_extra_frames().
C++ extension#
An extension with a Carbonite C++ plugin and pybind11 Python bindings. The plugin implements a Carbonite interface
(IExample) and the bindings expose acquire_example_interface() / release_example_interface() to Python.
The Python extension.py acquires the interface on startup, which triggers the Carbonite plugin to load.
Generated structure:
source/extensions/isaacsim.my.extension/
├── config/extension.toml
├── data/icon.png, preview.png
├── docs/api.rst, Overview.md, CHANGELOG.md
├── include/isaacsim/my/extension/IExample.h ← Carbonite interface
├── plugins/isaacsim.my.extension/ExamplePlugin.cpp ← Plugin implementation
├── bindings/isaacsim.my.extension/Bindings.cpp ← pybind11 bindings
├── python/
│ ├── __init__.py
│ ├── impl/__init__.py, extension.py
│ └── tests/__init__.py, test_extension.py
└── premake5.lua
The binding_module variable controls the pybind11 module name (e.g., my_extension produces _my_extension.so).
Key files to modify:
IExample.h— Define your Carbonite interface methods.ExamplePlugin.cpp— Implement the interface.Bindings.cpp— Expose additional methods to Python via pybind11.extension.py— The interface is already acquired on startup; add your Python-side logic.
OmniGraph extension#
An extension with both C++ and Python OmniGraph node definitions, a Carbonite plugin for C++ node registration, pybind11 bindings, and full OGN build integration.
The C++ nodes are defined by .ogn + .cpp pairs in nodes/. The Python nodes are defined by .ogn + .py
pairs in python/nodes/. The OGN build system auto-generates database classes and test scaffolding for both.
The Carbonite plugin implements INITIALIZE_OGN_NODES() / RELEASE_OGN_NODES() in carbOnPluginStartup() /
carbOnPluginShutdown(). The pybind11 bindings expose acquire_example_nodes_interface() which extension.py
calls on startup — this triggers the plugin to load and register the C++ OGN nodes.
Generated structure:
source/extensions/isaacsim.my.nodes/
├── config/extension.toml
├── data/icon.png, preview.png
├── docs/api.rst, Overview.md, CHANGELOG.md
├── include/isaacsim/my/nodes/IExampleNodes.h ← Carbonite interface
├── nodes/
│ ├── OgnExampleCpp.ogn, OgnExampleCpp.cpp ← C++ OGN node
│ ├── config/CategoryDefinition.json
│ └── icons/isaac-sim.svg
├── plugins/isaacsim.my.nodes/PluginInterface.cpp ← Plugin with OGN macros
├── bindings/isaacsim.my.nodes/Bindings.cpp ← pybind11 bindings
├── python/
│ ├── __init__.py
│ ├── impl/__init__.py, extension.py
│ ├── nodes/
│ │ ├── OgnExamplePython.ogn, OgnExamplePython.py ← Python OGN node
│ │ ├── config/CategoryDefinition.json
│ │ └── icons/isaac-sim.svg
│ └── tests/__init__.py, test_extension.py
└── premake5.lua
Unlike the C++ template, the OGN template does not use a separate binding_module variable. The OGN build system
automatically derives the bindings module name from the extension name (e.g., isaacsim.my.nodes →
_isaacsim_my_nodes).
Key files to modify:
.ognfiles — Define node inputs, outputs, and metadata (JSON format).OgnExampleCpp.cpp— Implement thecompute()method for the C++ node.OgnExamplePython.py— Implement thecompute()method for the Python node.To add more nodes, create new
.ogn+.cpp/.pypairs in the appropriatenodes/directory.
Tutorial: Creating and testing an extension#
This tutorial walks through creating a Python extension, building it, and verifying it works.
Step 1: Generate the extension#
Run the template generator:
./repo.sh template new
Select the Python extension template and enter values when prompted:
Extension name:
isaacsim.my.helloTitle:
Hello WorldVersion:
0.1.0Description:
A hello world extension.Category:
Examples
You should see:
Extension 'isaacsim.my.hello' created successfully in
source/extensions/isaacsim.my.hello
Step 2: Build#
Rebuild the project so the new extension is included:
./build.sh
The build system discovers the extension automatically — no manual registration required. A successful build ends with a summary similar to:
BUILD (release) SUCCEEDED (Took NN.NN seconds)
After the build, the extension is staged at:
_build/linux-x86_64/release/exts/isaacsim.my.hello/
If the build fails, re-run with ./build.sh -v for verbose output and inspect the first error
message — subsequent errors are usually cascading consequences of the first.
Step 3: Run the startup test#
Every generated extension includes a startup test. Run it from the build directory:
cd _build/linux-x86_64/release
./tests/tests-isaacsim.my.hello.sh
This runs two test suites:
A startup test that loads Kit with the extension enabled and verifies it starts and shuts down cleanly.
The unit tests in
isaacsim/my/hello/tests/test_extension.py.
A successful run ends with the standard unittest summary lines:
Ran N tests in N.NNs
OK
If a test fails, the failing test name and traceback are printed above the summary. Kit log files
written during the run can be found under ~/.nvidia-omniverse/logs/ for deeper inspection.
Step 4: Verify in Isaac Sim#
Launch Isaac Sim with your extension enabled:
./_build/linux-x86_64/release/isaac-sim.sh --enable isaacsim.my.hello
Extension Manager check
Open the Extensions Manager (Window > Extensions) and search for isaacsim.my.hello.
The extension should appear in the list with its status toggle set to enabled (green) and the
title and description from your extension.toml.
Script Editor check
From Window > Script Editor, run:
import omni.kit.app
ext_mgr = omni.kit.app.get_app().get_extension_manager()
print(ext_mgr.is_extension_enabled("isaacsim.my.hello"))
# Expected: True
Step 5: Customize#
Open source/extensions/isaacsim.my.hello/isaacsim/my/hello/extension.py and modify the
on_startup and on_shutdown methods to add your custom logic. Rebuild and re-test.
Verifying UI, C++, and OmniGraph extensions#
The verification flow is the same for every template (build → run startup test → launch and check in the Extensions Manager); the additional checks below confirm the template-specific surfaces.
UI extension
After enabling the extension, open Window > Examples Browser. Your extension should appear under
the category you selected at generation time (e.g., Examples). Selecting it opens a panel with
Load World and Reset buttons:
Click Load World — the simulation should load the default scene defined in
scenario.py::setup_scene()(a grid ground plane referenced fromIsaac/Environments/Grid/default_environment.usd) and physics callbacks become active.Click Reset — the world should return to its initial state and physics callbacks stop.
Replace the body of setup_scene() and on_physics_step() to drive your own simulation.
C++ extension
Verify the bindings work after building:
cd _build/linux-x86_64/release
./tests/tests-isaacsim.my.extension.sh
The unit tests import the bindings module and call the greet() method on the Carbonite interface.
You can repeat the check interactively from the Script Editor (substituting your own
extension_name and binding_module):
from isaacsim.my.extension.bindings._my_extension import acquire_example_interface
iface = acquire_example_interface()
print(iface.greet())
# Expected: a non-empty greeting string
OmniGraph extension
Verify both C++ and Python nodes are registered:
cd _build/linux-x86_64/release
./tests/tests-isaacsim.my.nodes.sh
The unit tests check that both node types appear in og.get_registered_nodes() and that their
compute() methods produce correct results.
You can also verify OGN nodes interactively via the Script Editor:
import omni.graph.core as og
nodes = og.get_registered_nodes()
print([n for n in nodes if "my.nodes" in n])
# Expected: ['isaacsim.my.nodes.ExampleCpp', 'isaacsim.my.nodes.ExamplePython']
To exercise the nodes graphically, open Window > Graph Editors > Action Graph, create a new
graph, and search the node palette for ExampleCpp / ExamplePython. Both should appear under
the category named after the first segment of your extension name (e.g., isaacsim), as defined
in the generated CategoryDefinition.json files.
Non-interactive usage (CI)#
For Continuous Integration (CI) automation, the full pipeline is:
Pre-accept the EULA. Both
./build.shand./repo.sh templateprompt on first use; in a non-interactive job the prompt blocks execution. Create both marker files in your CI setup step:touch .eula_accepted touch .omniverse_eula_accepted.txt
.eula_acceptedis checked bytools/eula_check.sh(used by./build.sh)..omniverse_eula_accepted.txtis checked by the template generator (used by./repo.sh template newand./repo.sh template replay).
Both are empty files that persist across builds, so creating them once at the start of the job is sufficient.
Note
If only
.eula_acceptedis present,./build.shwill work but./repo.sh template replaywill fail with an unhandled exception in the EULA prompt because the playback frontend cannot answer interactive prompts.Generate a playback file once, interactively. This step records the template selection and variable values into a TOML file:
./repo.sh template new --generate-playback my_extension.toml
Replay non-interactively in CI. The replay command consumes the playback file and produces the extension without prompting:
./repo.sh template replay my_extension.toml
Build and run the startup tests as in the tutorial above:
./build.sh cd _build/linux-x86_64/release ./tests/tests-<extension_name>.sh
Playback files use TOML (Tom’s Obvious, Minimal Language) format to specify the template and variable
values. The section header names the template (one of isaacsim-python-extension,
isaacsim-ui-extension, isaacsim-cpp-extension, isaacsim-omnigraph-extension):
[isaacsim-python-extension]
extension_name = "isaacsim.sensors.lidar"
title = "Lidar Sensor"
version = "0.1.0"
description = "Provides lidar sensor simulation."
category = "Sensors"
The templates/tests/ directory contains pre-defined playback files
(test_python_extension.toml, test_ui_extension.toml, test_cpp_extension.toml,
test_omnigraph_extension.toml) used for CI build verification — copy one as a starting point
when wiring up your own pipeline.
Template variables#
All templates share a common set of variables:
Variable |
Source |
Description |
|---|---|---|
|
User input |
Dotted extension name (e.g., |
|
User input |
Human-readable title |
|
User input |
Semantic version (e.g., |
|
User input |
Short description of the extension |
|
User input |
Extension category (e.g., |
|
User input (C++ only) |
pybind11 module name (e.g., |
|
Auto-derived |
Same as |
|
Auto-derived |
Dots replaced with slashes (e.g., |
|
Auto-derived |
First segment of |
|
Auto-generated |
Today’s date |
Note
The OmniGraph template does not use binding_module. The OGN build system automatically
derives the pybind11 module name from extension_name.
Build and verification checklist#
After generating an extension, always:
Build —
./build.shTest —
cd _build/linux-x86_64/release && ./tests/tests-<extension_name>.sh
For C++ and OmniGraph templates, the build compiles the Carbonite plugin and pybind11 bindings.
If the build fails, check that the premake5.lua include paths match your extension’s directory layout.
Troubleshooting#
- Extension not loading at runtime
Check that the extension name in
config/extension.tomlmatches the directory name undersource/extensions/. Verify the extension is enabled with--enable <extension_name>on the command line or through the Extensions Manager.- C++ plugin not loading
Ensure the
[[native.plugin]]section inextension.tomlincludespath = "bin/*.plugin". Without this, Kit cannot find the.soplugin file in the extension’sbin/directory.- C++ OGN nodes not registering
The Carbonite plugin must be started for
INITIALIZE_OGN_NODES()to run. This happens whenacquire_example_nodes_interface()is called from the pybind11 bindings inextension.py. If the C++ nodes don’t appear inog.get_registered_nodes(), verify that:Bindings.cpphas adefineInterfaceClass<IExampleNodes>callextension.pyimports and callsacquire_example_nodes_interface()inon_startup()PluginInterface.cppdoes not haveCARB_PLUGIN_IMPL_DEPS(this can prevent the plugin from loading)
- Build errors with OmniGraph nodes
Verify that
.ognfiles are valid JSON and that each C++.ognhas a matching.cppfile with theREGISTER_OGN_NODE()macro. Python.ognfiles need"language": "Python"and a matching.pyfile with acompute()method.- Stubgen failure during build
The stubgen step may fail with
generic_type: type "X" is already registerederrors. This is a known issue with the pybind11 stub generator when multiple extensions share type names. The build itself succeeds — only the.pyistub generation fails, which does not affect runtime.- Template replay fails with “This should not be encountered”
This means
.omniverse_eula_accepted.txtis missing from the repository root. The template generator’s playback frontend cannot answer the EULA prompt interactively, so it raises an exception in itsselect()method. Fix by creating the file:touch .omniverse_eula_accepted.txtThis is a separate file from
.eula_accepted(which is used by./build.sh). Both must exist for fully non-interactive CI pipelines.