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"]
}
FieldRequiredNotes
idyesSimple identifier ([A-Za-z0-9_][A-Za-z0-9_.-]*). Also the install folder name.
nameyesDisplay name.
versionyesDotted numeric (1.0.0). Drives the Manager’s update detection.
iconyesSee Icons below.
entry_pointyesPython file with register(api), relative to the folder.
description, author, homepagenoShown in the Manager.
kiosk_min_versionnoExtension is skipped (with a warning) on older Kiosk.
hooksnoAdvisory list: menubar, source_context, file_context.

Icons

The icon field resolves three ways, in order:

  1. Built-in core icon"icon": "builtin:<name>" references an icon Kiosk ships under Source/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 .png suffix is optional (builtin:convert == builtin:convert.png).

    Available names: convert, converter, video, light, texture, filter, database, cloud, tools, usd_viewer, puzzle. (_default is the fallback and is used automatically.)

  2. 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).

  3. 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

MethodReturns
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

MethodReturns / 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_task so the UI stays responsive — never block inside a callback.

Mutation — change the library

MethodDoes
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).

MethodAdds
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.

behind KioskMeet the maker →