Skip to content

Folder and Tag Methods

New in 2026.1 A plugin can ask to be told when folders are created, renamed or deleted, and when the tag library changes. Both are opt-in: a plugin that doesn't subscribe receives nothing.

The two subscriptions have deliberately different shapes. Folders are subscribed per object type, because each type has its own folder list. Tags are one global subscription, because there is a single tag library shared by every object type.

Plugins only

subscribeToChanges() on a folder list and indigo.server.subscribeToTagChanges() can only be called from a plugin. From a script or the interactive shell they raise ValueError.

There is no ownership filtering on either

deviceCreated() and friends check dev.pluginId, so a plugin only reacts to its own devices. Folders and tags have no owning plugin, so every subscribed plugin sees every change on the lists it subscribed to. The default implementations in plugin_base.py do nothing.

Subscribing to folder changes

Each object type's folder list is subscribed separately — subscribing to device folders does not subscribe you to variable folders. Subscribe only to the lists you actually use, in startup():

def startup(self):
    indigo.devices.folders.subscribeToChanges()
    indigo.variables.folders.subscribeToChanges()
    indigo.actionGroups.folders.subscribeToChanges()
    indigo.schedules.folders.subscribeToChanges()
    indigo.triggers.folders.subscribeToChanges()
    indigo.controlPages.folders.subscribeToChanges()

Telling the lists apart

There are six folder lists but one set of callbacks, so a plugin subscribed to more than one list needs to know which list a change came from. Every indigo.Folder carries a read-only parentType:

def folder_updated(self, orig_folder, new_folder):
    if new_folder.parentType != "device":
        return
    self.logger.info(f"device folder renamed: {orig_folder.name} -> {new_folder.name}")

Its value is one of "device", "variable", "actionGroup", "schedule", "trigger" or "controlPage" — the same names the command namespaces use.

What reaches these callbacks

Change Callback
Folder created folder_created
Folder renamed folder_updated
Folder's "display in remote UI" toggled folder_updated
Folder deleted folder_deleted
An object moved into or out of a folder Not a folder change. The object's folderId changed, so it arrives as deviceUpdated() or the equivalent for its type
Objects in a deleted folder Not deleted with it. They move to the top level, which again reaches you as ordinary object updates

folder_created()

Called when a folder is created in one of the lists you subscribed to.

Method

Method Required
folder_created(self, folder) No

Parameters

Parameter Description
folder an indigo.Folder object representing the folder that was created

Return Value:

Type Description
None This method does not return a value.

Command Syntax Examples

def folder_created(self, folder):
    self.logger.info(f"new {folder.parentType} folder: {folder.name}")

folder_updated()

Called when a folder is renamed, or its remote display setting is toggled. You get the folder both before and after the change, so you can see what actually changed rather than caching the previous state yourself.

Method

Method Required
folder_updated(self, orig_folder, new_folder) No

Parameters

Parameter Description
orig_folder an indigo.Folder object representing the folder before the change
new_folder an indigo.Folder object representing the folder after the change

Return Value:

Type Description
None This method does not return a value.

Command Syntax Examples

def folder_updated(self, orig_folder, new_folder):
    if orig_folder.name != new_folder.name:
        self.logger.info(f"renamed {orig_folder.name} -> {new_folder.name}")

folder_deleted()

Called when a folder is deleted. The objects that were in it are not deleted with it; they move to the top level, which reaches you as ordinary object updates.

Method

Method Required
folder_deleted(self, folder) No

Parameters

Parameter Description
folder an indigo.Folder object representing the folder that was deleted

Return Value:

Type Description
None This method does not return a value.

Renaming a folder from a plugin

There is no setName on the folder command namespace. That's deliberate, so folder edits go through the same modify-then-push path as every other object:

folder = indigo.devices.folders[folder_id]
folder.name = "New Name"
folder.replaceOnServer()

indigo.devices.folder does offer create, duplicate, delete and displayInRemoteUI — see the Folders reference. Calling setName on it raises AttributeError.

Subscribing to tag changes

One subscription, not one per type, because there is a single tag library shared by every object type. Call it once in startup():

def startup(self):
    indigo.server.subscribeToTagChanges()

Each tag is a one-entry map

Every tag callback receives a {name: colors} dictionary with a single entry, not a loose name and color. That's the same shape indigo.server.tags returns for the whole library, which is the point of the symmetry: a callback argument can be handed straight to code written against the library, because it is the library's shape with one entry in it.

The value is a dictionary carrying three colors:

{"color": "AABBCC", "text_color": "AABBCC", "border_color": "AABBCC"}

color is what the user chose and the only one stored. text_color and border_color are derived from it by the server, are read-only, and are what to draw a tag pill's text and border with. All three are bare "AABBCC" hex strings — the same form create_tag and update_tag accept for color. The dictionary could gain further keys without every plugin that unpacks one having to change.

def tag_created(self, tag):
    for name, colors in tag.items():
        self.logger.info(f"new tag {name} is #{colors['color']}, "
                         f"drawn with #{colors['text_color']} text")

What reaches these callbacks

Change Callback
Tag created in the library tag_created
Tag renamed and/or recolored tag_updated
Tag deleted from the library tag_deleted
A tag put on or taken off an object Not a library change. The object changed, so it arrives as deviceUpdated() or the equivalent for its type
Objects carrying a renamed or deleted tag Their tag lists are rewritten, which reaches you as ordinary object updates, in addition to the library callback
An update_tag that changes nothing Nothing at all. A no-op update isn't broadcast

A recolor reaches a plugin only through these callbacks

Recoloring a tag rewrites no object, so nothing on any object subscription fires for it. Everything else in the table above is also visible as object updates, if you happen to be watching the right objects — a recolor is not.

tag_created()

Called when a tag is added to the library.

Method

Method Required
tag_created(self, tag) No

Parameters

Parameter Description
tag a one-entry {name: colors} dictionary for the tag that was created

Return Value:

Type Description
None This method does not return a value.

tag_updated()

Called when a tag is renamed, recolored, or both at once. You get the tag both before and after, so whichever field didn't change simply repeats and a plugin never has to have cached the previous library.

Change What arrives
Recolor only same key in both, records differ
Rename only identical records, keys differ
Both at once one call, both differ

Method

Method Required
tag_updated(self, orig_tag, new_tag) No

Parameters

Parameter Description
orig_tag a one-entry {name: colors} dictionary for the tag before the change
new_tag a one-entry {name: colors} dictionary for the tag after the change

Return Value:

Type Description
None This method does not return a value.

Command Syntax Examples

def tag_updated(self, orig_tag, new_tag):
    orig_name, orig_record = next(iter(orig_tag.items()))
    new_name, new_record = next(iter(new_tag.items()))
    if orig_name != new_name:
        self.logger.info(f"renamed {orig_name} -> {new_name}")
    if orig_record["color"] != new_record["color"]:
        self.logger.info(f"recolored #{orig_record['color']} -> #{new_record['color']}")

tag_deleted()

Called when a tag is removed from the library, and therefore from every object carrying it. Its dictionary carries the colors the tag had at the moment it was removed.

Method

Method Required
tag_deleted(self, tag) No

Parameters

Parameter Description
tag a one-entry {name: colors} dictionary for the tag that was deleted

Return Value:

Type Description
None This method does not return a value.

These names are snake_case only

All six callbacks are snake_case, and there are no midCap aliases — no folderCreated, no tagUpdated. The older device and variable callbacks carry both spellings because they predate Indigo's move to snake_case. These are new, so there's no legacy name to stay compatible with.

A worked example

The SDK ships a plugin that subscribes to both and logs what arrives: Example Folder and Tag Subscriptions. See SDK Example Plugins.