Select Options and Light Effects Move to ConstVector
ESPHome 2026.10.0 stores select options and light effects in flash tables generated at build time instead of copying them onto the heap at boot. The getters now return a ConstVector view instead of a FixedVector. Code that binds the result with auto and uses the common read only members keeps working; code that names the old type, or calls front(), back(), capacity() or full(), needs a small change.
This is a breaking change for external components in ESPHome 2026.10.0 and later.
Background
Section titled “Background”Select options and light effects are fixed when the firmware is built, but both were copied into a heap FixedVector during setup(). On ESP8266 the list literal that codegen passed in also sat in .rodata, which is RAM there, so each list cost RAM twice. The API also built a temporary heap vector of effect names every time Home Assistant listed a light.
Codegen now emits each list as a table in flash, and the entity keeps a small ConstVector view of it (a pointer and a size). Selects with identical option lists share one table.
Changes
Section titled “Changes”- PR #20117: select options are stored in a shared flash table; adds the core
ConstVector<T, Owning>type. - PR #20204: light effects are stored in a flash table, and the API lists effect names straight from it.
What’s Changing
Section titled “What’s Changing”Select
Section titled “Select”// OLDconst FixedVector<const char *> &get_options() const;
// NEWconst SelectOptions &get_options() const; // SelectOptions = ConstVector<const char *, true>All set_options(...) overloads keep their behavior: they copy the list you pass, so runtime option lists from lambdas or external components work exactly as before. SelectTraits stays non copyable, as it was with FixedVector. A new set_options_static(table, count) is used by generated code only.
// OLDconst FixedVector<LightEffect *> &get_effects() const;void add_effects(const std::initializer_list<LightEffect *> &effects);
// NEWconst ConstVector<LightEffect *> &get_effects() const;void add_effects(LightEffect *const *effects, size_t count); // generated code onlyadd_effects() is only called by generated code; no external callers were found.
Who This Affects
Section titled “Who This Affects”Standard YAML configurations need no changes.
Lambdas and external components keep working if they use any of these, which covers every use found in a GitHub code search:
const auto &opts = id(my_select).traits.get_options();andconst auto &effects = id(my_light).get_effects();- range based
for,size(),empty(),[],at(),data() std::find(list.begin(), list.end(), value), includingconst auto *it = std::find(...): the iterators are raw pointers, as they were withFixedVector
Code that breaks is code that spells out the old type:
// OLD - no longer compilesconst FixedVector<const char *> &opts = id(my_select).traits.get_options();const FixedVector<LightEffect *> &effects = id(my_light).get_effects();
// NEW - let the compiler pick the typeconst auto &opts = id(my_select).traits.get_options();const auto &effects = id(my_light).get_effects();ConstVector also drops the front(), back(), capacity() and full() members that FixedVector had, so calls to them fail even when the result is bound with auto. For a non empty list, use list[0] instead of front() and list[list.size() - 1] instead of back(). The list never grows after it is set, so capacity() is the same as size() and full() is always true.
// OLD - no longer compilesconst char *first = id(my_select).traits.get_options().front();
// NEWconst auto &opts = id(my_select).traits.get_options();const char *first = opts[0]; // check opts.empty() first if the list can be emptyCopying the light effect list into a local variable (const auto effects = id(my_light).get_effects();) failed to compile since FixedVector was introduced; it compiles again now, because the light effect view is a cheap, non owning copy of a flash table. Copying a select’s options is still not allowed, since a select may own a runtime copy.
ConstVector
Section titled “ConstVector”ConstVector<T> in esphome/core/helpers.h is a read only view of a table: a pointer and a size, with raw pointer iterators. The owning variant, ConstVector<T, true>, can also hold a heap copy for lists set at runtime; it marks ownership in the top bit of the size, so it stays a pointer and a size (8 bytes on 32 bit targets). Element types must be whole words (pointers or other 32 bit values), because ESP8266 can only read flash with aligned 32 bit loads.
Other entity lists set at build time can move to ConstVector the same way.
Finding Code That Needs Updates
Section titled “Finding Code That Needs Updates”grep -rnE 'FixedVector<[[:space:]]*(const char|LightEffect)[[:space:]]*\*[[:space:]]*>|get_(options|effects)\(\)\.(front|back|capacity|full)\(' --include='*.cpp' --include='*.h' --include='*.yaml'The pattern also matches FixedVector<const char *> uses unrelated to selects, such as event types; only the ones holding select options need changing. It does not catch front() or back() called on a variable that holds the list, so check those by hand.
Timeline
Section titled “Timeline”- ESPHome 2026.10.0: select options (#20117) and light effects (#20204) use
ConstVector.
Questions?
Section titled “Questions?”If you have questions about these changes or need help migrating your external component, please ask in the ESPHome Discord or open a discussion on GitHub.
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.