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
Section titled “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
Section titled “What’s Changing”text_sensor and text (Minor change)
Section titled “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)
Section titled “select (Significant change)”// Before - received string and indexstd::function<void(std::string, size_t)>
// After - receives index onlystd::function<void(size_t)>Who This Affects
Section titled “Who This Affects”text_sensor and text
Section titled “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
Section titled “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
Section titled “Migration Guide”text_sensor and text (if using explicit types)
Section titled “text_sensor and text (if using explicit types)”// Beforestd::function<void(std::string)> callback = [](std::string value) { ESP_LOGD(TAG, "Value: %s", value.c_str());};text_sensor->add_on_state_callback(callback);
// Afterstd::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)
Section titled “select (Required update)”// Before - received string and indexthis->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 neededthis->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
Section titled “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
Section titled “Supporting Multiple ESPHome Versions”text_sensor and text
Section titled “text_sensor and text”// Lambda callbacks work unchanged for both versionstext_sensor->add_on_state_callback([](const std::string &value) { ESP_LOGD(TAG, "Value: %s", value.c_str());});select
Section titled “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()); });#endifTimeline
Section titled “Timeline”- ESPHome 2026.1.0 (January 2026): New callback signatures active
- No deprecation period for
select- signature changed directly
Finding Code That Needs Updates
Section titled “Finding Code That Needs Updates”# Find select callbacks with old signaturegrep -rn "add_on_state_callback.*std::string.*size_t" your_component/
# Find explicitly-typed function objectsgrep -rn "std::function<void(std::string)>" your_component/
# Find all state callback registrationsgrep -rn "add_on_state_callback" your_component/Questions?
Section titled “Questions?”If you have questions about migrating your external component, please ask in:
- ESPHome Discord - #devs channel
- ESPHome GitHub Discussions
Related Documentation
Section titled “Related Documentation”Copyright © 2026 ESPHome - A project from the Open Home Foundation
Comments
Feel free to leave a comment here to discuss this post with 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.