new_nav2_plugin
Scaffold a new Nav 2 plugin (controller, planner, behavior, smoother, goal-checker, progress-checker, costmap layer, or BT node). Wires pluginlib registration, parameter declaration on the lifecycle node, and a minimal integration test. Trigger when the user asks to write or extend a Nav 2 plugin.
git clone --depth 1 https://github.com/harunkurtdev/ros2-claude-code-template /tmp/new_nav2_plugin && cp -r /tmp/new_nav2_plugin/.claude/skills/new_nav2_plugin ~/.claude/skills/new_nav2_pluginSKILL.md
# Scaffolding a Nav 2 plugin
Nav 2 exposes every behaviour as a `pluginlib`-loaded class behind a
small set of `nav2_core::*` (or `nav2_costmap_2d::Layer`, or
`BT::ActionNodeBase`) interfaces. The recipe is the same shape for
every plugin kind — only the base class and the host server change.
## Decide first
| Plugin kind | Base class | Loaded by | Plugin description file |
|---------------------|---------------------------------------------|----------------------------|-------------------------|
| Controller | `nav2_core::Controller` | `controller_server` | `nav2_core_plugins.xml` |
| Global planner | `nav2_core::GlobalPlanner` | `planner_server` | `nav2_core_plugins.xml` |
| Smoother | `nav2_core::Smoother` | `smoother_server` | `nav2_core_plugins.xml` |
| Goal checker | `nav2_core::GoalChecker` | `controller_server` | `nav2_core_plugins.xml` |
| Progress checker | `nav2_core::ProgressChecker` | `controller_server` | `nav2_core_plugins.xml` |
| Behavior | `nav2_core::Behavior` | `behavior_server` | `nav2_core_plugins.xml` |
| Costmap layer | `nav2_costmap_2d::Layer` / `CostmapLayer` | `local_/global_costmap` | `nav2_costmap_2d_plugins.xml` |
| BT action / cond. | `BT::SyncActionNode` / `BT::ConditionNode` | `bt_navigator` | `nav2_tree_nodes.xml` |
See `.claude/skills/nav2_core_interfaces/SKILL.md` for the full method
list of each base.
## Step 1 — header
For a controller (other plugin kinds are analogous):
```cpp
// include/<pkg>/<snake_class>.hpp
#ifndef <PKG>__<SNAKE_CLASS>_HPP_
#define <PKG>__<SNAKE_CLASS>_HPP_
#include <memory>
#include <string>
#include <nav2_core/controller.hpp>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_lifecycle/lifecycle_node.hpp>
namespace <pkg>
{
class <ClassName> : public nav2_core::Controller
{
public:
<ClassName>() = default;
~<ClassName>() override = default;
void configure(
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
std::string name,
std::shared_ptr<tf2_ros::Buffer> tf,
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros) override;
void cleanup() override;
void activate() override;
void deactivate() override;
geometry_msgs::msg::TwistStamped computeVelocityCommands(
const geometry_msgs::msg::PoseStamped & pose,
const geometry_msgs::msg::Twist & velocity,
nav2_core::GoalChecker * goal_checker) override;
void setPlan(const nav_msgs::msg::Path & path) override;
void setSpeedLimit(const double & speed_limit, const bool & percentage) override;
private:
rclcpp_lifecycle::LifecycleNode::WeakPtr node_;
std::string plugin_name_;
std::shared_ptr<tf2_ros::Buffer> tf_;
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros_;
// Declared params
double desired_linear_vel_{0.0};
};
} // namespace <pkg>
#endif
```
## Step 2 — source
```cpp
// src/<snake_class>.cpp
#include "<pkg>/<snake_class>.hpp"
#include <pluginlib/class_list_macros.hpp>
namespace <pkg>
{
void <ClassName>::configure(
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
std::string name,
std::shared_ptr<tf2_ros::Buffer> tf,
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
{
node_ = parent;
plugin_name_ = name;
tf_ = tf;
costmap_ros_ = costmap_ros;
auto node = node_.lock();
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".desired_linear_vel",
rclcpp::ParameterValue(0.5));
node->get_parameter(plugin_name_ + ".desired_linear_vel",
desired_linear_vel_);
}
void <ClassName>::cleanup() {}
void <ClassName>::activate() {}
void <ClassName>::deactivate() {}
geometry_msgs::msg::TwistStamped
<ClassName>::computeVelocityCommands(
const geometry_msgs::msg::PoseStamped & /*pose*/,
const geometry_msgs::msg::Twist & /*velocity*/,
nav2_core::GoalChecker * /*goal_checker*/)
{
geometry_msgs::msg::TwistStamped cmd_vel;
cmd_vel.header.stamp = node_.lock()->now();
cmd_vel.twist.linear.x = desired_linear_vel_;
return cmd_vel;
}
void <ClassName>::setPlan(const nav_msgs::msg::Path & /*path*/) {}
void <ClassName>::setSpeedLimit(const double & /*speed_limit*/,
const bool & /*percentage*/) {}
} // namespace <pkg>
PLUGINLIB_EXPORT_CLASS(<pkg>::<ClassName>, nav2_core::Controller)
```
Critical points:
* **Always** use `nav2_util::declare_parameter_if_not_declared` —
Nav 2 calls `configure` more than once across lifecycle transitions.
* Parameters are keyed under `<plugin_name>.<param>` because users set
them under the plugin alias they pick in YAML, not under the plugin
class name.
* `PLUGINLIB_EXPORT_CLASS` last argument must match the loader's
expected base class (`nav2_core::Controller`,
`nav2_costmap_2d::Layer`, …).
## Step 3 — pluginlib manifest (`plugins.xml`)
```xml
<library path="<pkg>">
<class
type="<pkg>::<ClassName>"
base_class_type="nav2_core::Controller">
<description>
One-line description of what this controller does.
</description>
</class>
</library>
```
## Step 4 — `CMakeLists.txt`
```cmake
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(pluginlib REQUIRED)
find_package(nav2_core REQUIRED)
find_package(nav2_costmap_2d REQUIRED)
find_package(nav2_util REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
add_library(${PROJECT_NAME} SHARED src/<snake_class>.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(${PROJECT_NAME}
rclcpp rclcpp_lifecycle pluginlib nav2_coUse proactively before opening a PR that adds or changes BehaviorTree.CPP nodes or BehaviorTree.ROS2 wrappers (RosActionNode/RosServiceNode/RosTopicPub/SubNode, TreeExecutionServer). Reviews a diff against BT.CPP v4 conventions — node base-class choice, non-blocking ticks, ports/blackboard typing, factory/plugin registration, XML v4, and the ROS 2 wrapper contract. Returns a punch list with file:line anchors, not a rewrite.
Use when a design decision touches Clean Architecture boundaries in a ROS 2 project — which layer a new behaviour belongs to, whether a port belongs in domain or application, whether a new node should be lifecycle-managed, whether to compose nodes or split packages. Returns an architectural recommendation with trade-offs, not implementation.
Use when a design decision touches the gz-sim ECS — where new state should live, which system phase should write it, how to avoid coupling, whether to add a component vs. a member variable, whether a new system should be split or merged with an existing one. Returns an architectural recommendation with trade-offs, not implementation.
Use proactively before opening any gz-sim PR. Reviews a diff against the project's C++17 style, ECS conventions, plugin registration patterns, CMake structure, test placement, Migration.md / Changelog.md expectations, and pre-commit configuration. Returns a punch list, not a rewrite.
Use proactively before opening a PR that adds or changes a ros2_control controller, broadcaster, or hardware component (incl. URDF <ros2_control> bringup). Reviews a diff against ros2_controllers / ros2_control_demos conventions — controller & hardware lifecycle, command/state interface configuration, real-time safety of update()/read()/write(), generate_parameter_library usage, pluginlib registration, chainable-controller correctness, URDF wiring, and tests. Returns a punch list with file:line anchors, not a rewrite.
Use proactively before opening any ROS 2 / Nav 2 PR. Reviews a diff against this template's Clean Architecture, ROS 2 communication, lifecycle, testing, and Nav 2 plugin conventions. Returns a punch list with file:line anchors, not a rewrite.
Use proactively before opening a PR that touches a VDA 5050 connector / fleet bridge. Reviews a diff against VDA 5050 v3.0.0 protocol compliance (topics, QoS, header rules, base/horizon, action state machine, schema validation) and the template's Clean Architecture for the MQTT↔Nav 2 bridge. Returns a punch list with file:line anchors, not a rewrite.
Build the colcon workspace (optionally a single package) and report the outcome.