Skip to content

Callback Signature Changes for text_sensor, text, and select

The callback signatures for text_sensor, text, and select components have been updated to reduce heap allocations. The select component callback signature has changed significantly.

This is a breaking change for external components in ESPHome 2026.1.0 and later.

Background

These PRs optimize state callbacks to reduce heap churn:

PR #12503: text_sensor Changes callback from void(std::string) to void(const std::string &).

PR #12504: text Changes callback from void(std::string) to void(const std::string &).

PR #12505: select Changes callback from void(std::string, size_t) to void(size_t) - string parameter removed entirely.

What's Changing

text_sensor and text (Minor change)

// Before - passed by value (copies string)
std::function<void(std::string)>

// After - passed by const reference (no copy)
std::function<void(const std::string &)>

select (Significant change)

// Before - received string and index
std::function<void(std::string, size_t)>

// After - receives index only
std::function<void(size_t)>

Who This Affects

text_sensor and text

Most code requires no changes. Lambda callbacks continue working with either signature:

// Both still work:
sensor->add_on_state_callback([](const std::string &value) { ... });
sensor->add_on_state_callback([](std::string value) { ... });  // Still works (copy made)

Breaking case: Explicitly-typed std::function variables must be updated. This is extremely rare in practice.

select

All callbacks must be updated. The string parameter has been removed entirely.

Standard YAML configurations are not affected - on_value triggers in YAML still receive both x (string) and index.

Migration Guide

text_sensor and text (if using explicit types)

// Before
std::function<void(std::string)> callback = [](std::string value) {
  ESP_LOGD(TAG, "Value: %s", value.c_str());
};
text_sensor->add_on_state_callback(callback);

// After
std::function<void(const std::string &)> callback = [](const std::string &value) {
  ESP_LOGD(TAG, "Value: %s", value.c_str());
};
text_sensor->add_on_state_callback(callback);

select (Required update)

// Before - received string and index
this->select_->add_on_state_callback([](const std::string &value, size_t index) {
  ESP_LOGD(TAG, "Selected: %s (index %zu)", value.c_str(), index);
});

// After - receives index only, get string from select if needed
this->select_->add_on_state_callback([this](size_t index) {
  const char *value = this->select_->option_at(index);  // Returns const char*, no allocation
  if (value != nullptr) {
    ESP_LOGD(TAG, "Selected: %s (index %zu)", value, index);
  }
});

// Or if you only need the index (common case)
this->select_->add_on_state_callback([this](size_t index) {
  this->handle_selection(index);
});

Getting the selected option string from index

If your callback needs the option string, retrieve it from the select entity:

this->select_->add_on_state_callback([this](size_t index) {
  // Option 1: Use option_at()
  const char *value = this->select_->option_at(index);
  if (value != nullptr) {
    // use value
  }

  // Option 2: Use traits directly
  const auto &options = this->select_->traits.get_options();
  if (index < options.size()) {
    const char *value = options[index];
    // use value
  }
});

Supporting Multiple ESPHome Versions

text_sensor and text

// Lambda callbacks work unchanged for both versions
text_sensor->add_on_state_callback([](const std::string &value) {
  ESP_LOGD(TAG, "Value: %s", value.c_str());
});

select

#if ESPHOME_VERSION_CODE >= VERSION_CODE(2026, 1, 0)
  // New API - index only
  this->select_->add_on_state_callback([this](size_t index) {
    const char *value = this->select_->option_at(index);
    if (value != nullptr) {
      ESP_LOGD(TAG, "Selected: %s", value);
    }
  });
#else
  // Old API - string and index
  this->select_->add_on_state_callback([](const std::string &value, size_t index) {
    ESP_LOGD(TAG, "Selected: %s", value.c_str());
  });
#endif

Timeline

  • ESPHome 2026.1.0 (January 2026): New callback signatures active
  • No deprecation period for select - signature changed directly

Finding Code That Needs Updates

# Find select callbacks with old signature
grep -rn "add_on_state_callback.*std::string.*size_t" your_component/

# Find explicitly-typed function objects
grep -rn "std::function<void(std::string)>" your_component/

# Find all state callback registrations
grep -rn "add_on_state_callback" your_component/

Questions?

If you have questions about migrating your external component, please ask in:

Comments

Feel free to leave a comment here to discuss this post wth others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.