Develop Extensions
Build your own Kiosk extension — manifest, register(api), icons, and loading it from a local folder.
Extensions are optional, in-process Python modules that extend Kiosk’s own UI (menubar entries, source-context actions, file-context actions). Kiosk core runs fully without any extensions. They are not shipped in the installer — users install them on demand from the Extension Manager (App → Extensions → Extension Manager…), which fetches the online catalog when it opens.
Each extension is a folder with a manifest.json and a Python entry point that exposes a top-level register(api) function.
Building for an AI assistant? Download the full guide as raw Markdown and hand it to your LLM of choice: extension-development.md. The companion plugin guide is at plugin-development.md.
manifest.json
{
"id": "my_extension",
"name": "My Extension",
"version": "1.0.0",
"icon": "builtin:convert",
"entry_point": "extension.py",
"description": "What it does, in one line.",
"author": "Your Name",
"homepage": "https://example.com",
"kiosk_min_version": "1.2.0",
"hooks": ["menubar", "source_context", "file_context"]
}
| Field | Required | Notes |
|---|---|---|
id | yes | Simple identifier ([A-Za-z0-9_][A-Za-z0-9_.-]*). Also the install folder name. |
name | yes | Display name. |
version | yes | Dotted numeric (1.0.0). Drives the Manager’s update detection. |
icon | yes | See Icons below. |
entry_point | yes | Python file with register(api), relative to the folder. |
description, author, homepage | no | Shown in the Manager. |
kiosk_min_version | no | Extension is skipped (with a warning) on older Kiosk. |
hooks | no | Advisory list: menubar, source_context, file_context. |
Icons
The icon field resolves three ways, in order:
-
Built-in core icon —
"icon": "builtin:<name>"references an icon Kiosk ships underSource/Icons/extensions/. These resolve without downloading the extension, so they render in the Manager’s catalog before install. Use this if you don’t want to draw your own icon. The.pngsuffix is optional (builtin:convert==builtin:convert.png).Available names:
convert,converter,video,light,texture,filter,database,cloud,tools,usd_viewer,puzzle. (_defaultis the fallback and is used automatically.) -
Bundled icon — any other value (e.g.
"icon": "icon.png") is a path relative to your extension folder, for custom branding. It only renders once the extension is installed (the catalog shows the default placeholder until then). -
Fallback — if the icon can’t be resolved, the extension still loads and uses the core
_default.png. A missing icon is a warning, never a load failure.
register(api)
register is called once at startup with the KioskExtensionAPI facade — the only surface extensions should touch. Register UI hooks here:
def register(api):
api.register_menubar_entry("Do Thing…", on_do_thing)
api.register_file_context_action("Process…", on_process, predicate=is_image)
api.register_source_context_action("Batch…", on_batch)
Failures in register() are trapped and surfaced in the Extension Manager footer — a broken extension never crashes Kiosk.
The api object
The api passed to register(api) is a KioskExtensionAPI instance — the only supported contract. Internal Kiosk objects are private on purpose, so the core can change without breaking shipped extensions. It has four areas.
Query — read library state
| Method | Returns |
|---|---|
get_categories() | All category names, in display order. |
get_current_category() | The category currently active in the UI. |
get_sources(category=None) | List of source dicts for a category (current category if omitted). |
get_files(category=None, source=None) | List of file_info dicts — one source, a whole category, or the current category. |
get_file_info(file_path) | The file_info record for one file. |
get_selected_files() | file_info dicts for the currently selected tiles. |
is_polyhaven_texture(file_info) | True if the file is a Poly Haven texture set that isn’t downloaded yet. |
A file_info is Kiosk’s database record for a file — keys include file_path, file_name, base_name, extension, source_path, category, thumbnail_path, and grouping flags (is_texture_set, is_image_sequence, …).
Tooling — bundled binaries & helpers
| Method | Returns / does |
|---|---|
get_ffmpeg_path() · get_ffprobe_path() | Absolute path to the bundled ffmpeg / ffprobe. |
get_oiio() | The already-loaded OpenImageIO module. |
get_user_data_dir() | %APPDATA%\Kiosk. Create a subfolder for your settings/presets — it survives updates. |
get_logger(suffix='') | A logger namespaced to your extension (kiosk.ext.<id>). |
get_task_helpers() | Dict of reusable helpers: image/video metadata and video thumbnail sequences. |
run_task(fn, *args, on_done=None, on_progress=None, on_error=None, **kwargs) | Run fn on Kiosk’s thread pool; callbacks fire on the GUI thread. |
resolve_texture_set(file_info, on_done=None, on_progress=None, on_error=None) | Ensure a texture set is local (downloads a Poly Haven set if needed), then call on_done with the updated file_info. |
Anything slow (ffmpeg, OIIO, downloads) should run through
run_taskso the UI stays responsive — never block inside a callback.
Mutation — change the library
| Method | Does |
|---|---|
rename_files(renames) | Rename a list of (old_path, new_path) pairs on disk and keep the database and thumbnails in sync. Returns {'renamed': int, 'errors': [(path, reason), …]}. |
Registration — add UI
Call these only inside register(api). Each icon is optional (a builtin: name or a path inside your extension folder).
| Method | Adds |
|---|---|
register_menubar_entry(label, callback, icon='', shortcut='') | An entry under your submenu in the Extensions menu. callback() takes no arguments. |
register_source_context_action(label, callback, icon='', predicate=None) | An action in a source’s context menu. predicate(source_dict) -> bool filters which sources show it; callback(ctx) receives {source_name, category, source}. |
register_file_context_action(label, callback, icon='', predicate=None) | An action in the file context menu. predicate(file_info_list) -> bool gates visibility; callback(file_info_list) receives the file(s) the menu opened against. |
register_file_context_submenu(label, provider, icon='', predicate=None) | A nested submenu built fresh each time it opens: provider() returns a list of (item_label, item_callback), and each item_callback(file_info_list) runs when chosen. Use this when items change at runtime (e.g. presets read from disk). |
A worked example
def register(api):
log = api.get_logger()
# Only show for image files.
def is_image(file_info_list):
return all(fi.get("extension", "").lower() in ("png", "jpg", "exr", "tif")
for fi in file_info_list)
# Read resolution off the UI thread, then log it.
def show_resolution(file_info_list):
helpers = api.get_task_helpers()
for fi in file_info_list:
api.run_task(
helpers["get_image_resolution"], fi["file_path"],
on_done=lambda res, p=fi["file_path"]: log.info(f"{p}: {res}"),
)
api.register_file_context_action("Show resolution", show_resolution, predicate=is_image)
Loading locally
For personal or studio use you don’t need to publish anything. In the Extension Manager, click Browse and point Kiosk at a folder on disk — the same workflow as custom plugins. Every subfolder with a valid manifest.json is loaded on startup.
Put your extension folder (or a parent folder containing several extensions) anywhere you like — a dev checkout, a network share, or a synced pipeline folder. Restart Kiosk after changes.