Skip to content
hikari
RenderingEditorAIToolchainDocumentation
GitHub
hikari

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsLicenseBack to top
Documentation / Guides
Browse docs
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Guides11 min read

Plugins

On this page
On this pagePlugin surfacesLayoutManaging plugins in the editorCreate a plugin packageUse a plugin from game codeLifecycle hooksModule rolesEditor contributionsNative middleware variantsBuild and packed-editor workflowPackagingWhat plugins are not Back to top

Hikari plugins are source-composed packages. Enabling a plugin adds Zig modules and native middleware declarations to the project game module build; it does not install a runtime Zig plugin loader.

For composition design and invariants, see Source-composed plugin system. For the author-facing product (editor scene API, render extensions, gameplay polish), start at the Plugin API guide; normative design: Plugin API design.

Plugin surfaces

Product name: Hikari Plugin API. Contracts remain federated and typed — there is no runtime hi.plugin mega-vtable. Plugins compose into the game module, then call typed SDK paths (hi.editor, hikari_game, hi.render). Related bounds: design non-goals; front door: Plugin API guide; design: Plugin API design.

DomainConcernWhereWhat you get
—Enable / composethis guide + designmanifests, roles, packaging
GameplayRuntime APIshikari_gamesame as game code
GameplayPackage settingshi.settings + Plugin API.engine/plugin-settings/<id>.json
GameplayLifecycleLifecycle hooks + sdk/src/plugin_compose.zigcreate / tick / scene / …
EditorChromehi.editor + UI and editoractions, menus, toolbar, panels, surfaces
EditorScene authoringhi.editor.scene() + Plugin APItransactional edits, one undo, revision checks
RenderingTemporal upscalereconstruction provider + renderingregister in onLaunch
RenderingPost-processhi.render().registerPostProcessPass + renderingfixed slots, opaque refs, host encoder tint
GameplayExplicit native loadhi.shared_lib + native variantsstage-only shared libs
GameplayOptional depshikari_plugin_optionscompile-time enabled

Maintenance: when seams multiply, add a row here. Never consolidate federated plugin runtime surfaces into one API for cleanup alone — keep seams named and typed; a unified hi.plugin is not a tidy-up goal.

Layout

Project:

text
src/games/example/
  hikari.project.json
  hikari.plugins.json
  src/
    root.zig
    session.zig
  plugins/
    example_capability/
      hikari.plugin.json
      runtime/root.zig
      editor/root.zig

hikari.plugins.json enables package directories:

json
{
  "kind": "com.hikari.plugins",
  "version": 1,
  "plugins": [
    {
      "path": "plugins/example_capability",
      "enabled": true
    }
  ]
}

Each package has its own hikari.plugin.json:

json
{
  "kind": "com.hikari.plugin",
  "version": 1,
  "id": "com.hikari.example_capability",
  "package_version": "1.0.0",
  "display_name": "Example Capability",
  "sdk_compat": "1",
  "modules": [
    {
      "import": "hikari_plugin_example",
      "role": "runtime",
      "source": "runtime/root.zig"
    },
    {
      "import": "hikari_plugin_example_editor",
      "role": "editor",
      "source": "editor/root.zig",
      "deps": [
        { "import": "hikari_plugin_example", "public": true }
      ]
    }
  ]
}

version is the manifest file format. sdk_compat is the Hikari SDK generation your Zig sources are written against — it is optional, but when present it must match the SDK exactly, and a mismatch fails the build rather than compiling your package against an API it does not target. Optional supported_os values (windows, macos, linux, any) advertise host compatibility to Project Settings. It is a UI hint rather than a build gate: a cross-platform package may remain enabled while its runtime stays inert on an unsupported host.

Managing plugins in the editor

Project Settings → Plugins lists every package it can find: those listed in hikari.plugins.json, plus any package sitting under plugins/ that nobody has enabled yet. Each card shows the display name, id, version, and path; capability chips derived from the package's module roles (Runtime, Editor UI, Entities, Native, Middleware); and what changing it costs — Reloads with the game module, Stays loaded until the editor restarts (process_pinned), or Restart required to change (restart_required).

A package the build would reject is shown dimmed with the reason (invalid manifest, unreadable manifest, or built for a different SDK generation) and cannot be switched on — only off. A package whose supported_os excludes the current host is marked Incompatible with this platform; if it was already enabled, the editor preserves that project setting so its inert cross-platform wrapper can keep the project portable.

Toggling is an ordinary Project Settings edit: nothing is written until Save, Revert restores the scanned state, and the footer reports the reopen boundary, because enabling or disabling a package recomposes the game module. Save writes hikari.plugins.json — the same file you would hand-edit, and still the only project-level source of truth for enablement.

The page reads manifests for display only. The resolver in sdk/build/plugins/ remains the authority on what actually composes, so the build is still what tells you a graph is invalid.

Create a plugin package

Canonical skeleton: src/games/example/plugins/_template/ (copy, rename ids/imports, enable in hikari.plugins.json).

  1. Create a package directory under the project, usually plugins/<name>/.
  2. Add hikari.plugin.json.
  3. Add a runtime Zig root for game-facing code.
  4. Add optional editor Zig root for authoring surfaces.
  5. Enable the package from the project hikari.plugins.json.

Runtime module example:

zig
const std = @import("hikari_std");
const hi = @import("hikari_game");

pub fn hashBytes(bytes: []const u8) u64 {
    return std.hash.Wyhash.hash(0, bytes);
}

pub const Plugin = struct {
    allocator: std.mem.Allocator,

    pub fn create(allocator: std.mem.Allocator) !*Plugin {
        const self = try allocator.create(Plugin);
        self.* = .{ .allocator = allocator };
        return self;
    }

    pub fn destroy(self: *Plugin) void {
        self.allocator.destroy(self);
    }

    pub fn onLaunch(self: *Plugin) void {
        _ = self;
    }

    pub fn onTick(self: *Plugin) void {
        _ = self;
    }
};

Product Zig rules still apply: import hikari_std, not Zig std directly.

Use a plugin from game code

The module import field becomes the Zig import name:

zig
const example_plugin = @import("hikari_plugin_example");

pub fn update() void {
    const h = example_plugin.hashBytes("hello");
    _ = h;
}

The plugin is compiled into the same game module generation as the project code. There is no runtime lookup by package id.

Optional dependency presence is a compile-time query:

zig
const plugin_options = @import("hikari_plugin_options");

const has_steam = plugin_options.enabled("hikari_plugin_steam");

enabled answers for this product, not for the manifest: it is true only for modules the current build actually composes. An editor module reports false in a game build, and a third_party module reports false when no composed module depends on it. That makes it safe to gate an optional @import on.

Every source plugin receives only hikari_game, hikari_std, hikari_plugin_options, and its declared module dependencies.

Lifecycle hooks

If the runtime root exports pub const Plugin = struct { ... }, generated glue calls optional methods in dependency order:

HookWhen
create(allocator)Game module/plugin container creation.
destroy()Reverse order during shutdown or failed create cleanup.
onLaunch()Runtime launch.
onWorldAttach(WorldScope)A World is fully bound, before its scene actors instantiate. The subsystem is one instance shared by every World — the editor attaches the authored World and the Play copy at the same time — so key per-World state by scope.id rather than assuming a singleton. scope.role is .editor, .play, or .standalone.
onWorldDetach(WorldScope)Reverse order immediately before that World's final teardown, with HostApi still bound to it.
onTerminate()Runtime terminate.
onTick()Per game tick.
onPlayEnded()Editor Play -> Edit stop path.
onSceneWillUnload()Scene unload notification.
onSceneLoaded(info: hi.SceneInfo)Scene loaded.
onSceneLayerLoaded(key)Additive layer presented — its content is live.
onSceneLayerReady(key)A manual layer reached its gate and awaits presentSceneLayer.
onSceneLayerWillUnload(key)Additive layer unload.
onSceneReadyStage(key, stage)Scene readiness progress.

Dependencies are called first; shutdown is reverse order. If two modules are otherwise unordered, the resolver orders by package id then import name.

Module roles

RoleUse
runtimeGame-facing API, entities, lifecycle hooks. Included in game products and editor game-module builds.
editorEditor menus/panels/actions for the project. Included only when editor contribution is enabled.
developerLocal development helpers. Not part of product composition by default.
third_partyNative middleware declaration only; no Zig source.

Dependency rules:

  • Runtime may depend on runtime and third_party modules only.
  • Editor may depend on runtime, editor, and third_party modules.
  • Developer may depend on anything, but is not selected for product builds by default.
  • Third_party may depend only on third_party.

entities and components are valid only on runtime modules. register_editor is valid only on editor modules.

A package's entities directory is generated into its own module and reaches the game root as hikari_plugin_entities. The project's own sources are discovered wholesale into content_manifest.gen.zig (any path under src/). Both are walked at content registration, so plugin archetypes register alongside project archetypes.

A package's optional components directory follows the same pattern and reaches the game root as hikari_plugin_components. Plugin files must end in _entity.zig / _component.zig (each is a separate module root outside the package sandbox); manifests are sorted. Plugin components register before project and plugin archetypes.

Editor contributions

Editor modules should use the existing hi.editor contribution ABI. Keep editor-only code gated so standalone game products never analyze it.

Pattern:

zig
const hi = @import("hikari_game");
const runtime = @import("hikari_plugin_example");

pub fn registerEditor(api: *const hi.editor.Api) void {
    // Always first: refuses a host whose table this SDK generation cannot read.
    api.check() catch return;
    _ = runtime.hashBytes("editor");
    // Register actions, menus, toolbar items, panels, and surface controls
    // through the checked accessors (`api.registerAction`, `api.beginSurface`, …).
}

Full capability table and isolation rules: Game editor SDK. Working sample (chrome + builtin scene transaction demo): src/games/example/plugins/example_capability/editor/root.zig. Product overview: Plugin API.

Rules:

  • Call api.check() before anything else (api_version 4); it returns error{IncompatibleEditorApi} on an older host and installs hi.editor.scene() generation state. Entry points added after api_version 1 are nullable; reach them through accessors so a missed version check fails cleanly.
  • The *const Api is a stack temporary. Copy the table (api.*) if you need show_message / set_control_* later; those stay valid for the life of the generation, but are editor-UI-thread only.
  • Register structural chrome at create time only (actions, toolbar items, surface controls).
  • Host owns retained chrome, menus, toolbars, docks, strings, and accessibility.
  • Plugin ids/labels are copied or interned by the host.
  • On game-module reload, callbacks rebind; structural panels/menus/toolbars/surfaces do not change after freezeChrome.
  • Prefer registerAction (ActionInvokeFn with host-filled ActionContext) + menu/toolbar entries that reference the action id; use beginSurface / addControl / endSurface for simple panel bodies (labels, buttons, toggles, row/column markers). That surface API is the design’s SurfaceBuilder model — a POD command recorder, not retained-widget ownership. There is no register_command / CommandFn dual path.
  • Scene authoring: hi.editor.scene() → buffer-until-commit EditTransaction → shared host applyBatch (one undo label). Do not hold raw SceneDocument* / EditorApp*.
  • Use host actions when they exist; custom actions use open string ids and plugin callbacks.
  • Do not call host contrib.zig / bindFill or keep retained widget pointers from plugin code.

Native middleware variants

Declare native SDKs under native variants. The build selects the best match for OS, arch, config, and product mode.

Static library:

json
{
  "import": "hikari_plugin_foo_native",
  "role": "third_party",
  "native": [
    {
      "os": "macos",
      "arch": "aarch64",
      "libraries": [
        {
          "linkage": "static",
          "static_lib": "native/macos/libfoo.a",
          "reload_policy": "generation_local"
        }
      ],
      "licenses": [
        { "path": "LICENSE.txt", "dest": "licenses/foo.txt" }
      ]
    }
  ]
}

Windows linked shared library:

json
{
  "linkage": "shared",
  "load_mode": "linked",
  "import_lib": "native/windows/foo.lib",
  "shared_lib": "native/windows/foo.dll",
  "reload_policy": "process_pinned"
}

Explicitly loaded shared library:

json
{
  "linkage": "shared",
  "load_mode": "explicit",
  "shared_lib": "native/windows/foo_optional.dll",
  "reload_policy": "restart_required"
}

Rules:

  • static_lib is only for linkage: "static".
  • import_lib is a link input for shared + linked (mainly Windows).
  • shared_lib is the runtime file staged beside the game module (dest = basename).
  • shared + explicit is stage-only and must not declare import_lib; same adjacent dest rule; open via hi.shared_lib.
  • runtime_files and licenses use author-relative dest under the same game-module root.

Ship layout (game product, editor install tree, and each hot-reload generation):

text
game.dll | libgame.dylib
vendor_loader.dll          # linked / explicit shared_lib
licenses/...               # license dests
hikari-plugin-artifacts.json

No PATH / DYLD_* / LD_LIBRARY_PATH for players or tools. Zig installs each artifact once under the module install subdir; Kaji consumes the generated link closure and copies the same dest map into the product root.

Loader / dependency resolution:

PlatformMechanism
WindowsHost opens game.dll with LoadLibraryEx + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR so dependents resolve from the module directory. Explicit loads in hikari_game.shared_lib use the same flags for absolute paths.
macOS / Linuxdlopen plus link-time @loader_path / $ORIGIN rpath on the game module so dependents resolve beside the loaded image.

Reload policies:

PolicyUse
generation_localSafe with game-module reload.
process_pinnedLoaded once for the process; editor restart not usually required for code using the same binary.
restart_requiredChanging or rebinding requires a full editor/product restart.

Build and packed-editor workflow

Normal game build:

bash
bin/kaji/kaji game --workspace="$PWD" --project="$PWD/src/games/example" --type=dynamic --config=debug

Editor with project:

bash
bin/kaji/kaji editor --workspace="$PWD" --project="$PWD/src/games/example" --type=dynamic --config=debug --run

Packed editor that can compile project plugins offline:

bash
bin/kaji/kaji editor --workspace="$PWD" --type=dynamic --config=release \
  --vendor:zig=fetch+bundle --vendor:engine=bundle --package=bundle

Important behavior:

  • The packed editor does not ship arbitrary plugin packages. Users open a project, and the editor compiles that project's enabled plugins.
  • Project/plugin source is trusted code. It runs in the editor process after compile/load. A packed editor refuses the first compile until the user explicitly grants trust; after reviewing the enabled package sources, relaunch once with HIKARI_TRUST_PROJECT=<digest> (the digest is printed in the editor log / compile pane).
  • Trust is stored outside the project in the user's persistent Hikari data and is keyed by that digest of project game source plus every enabled plugin file. Changing a manifest, source file, or native artifact invalidates it. A project cannot self-authorize with a file under .engine/.
  • If a plugin declares native middleware, its selected link and stage artifacts come from the generated hikari-plugin-artifacts.json.
  • Without bundled Zig, the editor discovers a host toolchain at runtime as described in Editor project selector.

Packaging

Game and editor packaging copy the composed product stage. Plugin native/runtime/license files enter that stage only through:

text
hikari-plugin-artifacts.json

Do not add parallel package copy rules for plugin folders. A plugin package's source tree is not product data by default. If a runtime file, license, or shared library must ship, declare it in hikari.plugin.json so it appears in the artifact manifest.

Host packaging remains separate from resource layout:

ConcernMechanism
Compile/link plugin ZigSource composition into game module.
Link/stage native middlewarehikari-plugin-artifacts.json.
Ship cooked game data`--resources=bundles
Emit host artifact`--package[=bundle

What plugins are not

  • Not runtime-loaded Zig modules.
  • Not a service locator.
  • Not a way to get raw GPU/native renderer handles (reconstruction on_frame is the exception).
  • Not a replacement editor shell.
  • Not plugin-owned Shinra asset roots.
  • Not special engine cases for Steamworks, platform commerce, analytics, or similar SDKs. Those are plugin packages that consume this foundation.
  • Not open render-graph plugins (adding passes / owning frame resources) yet. Temporal reconstruction uses a dedicated Zig-only path: registerReconstructionProvider + on_config / on_frame (plugin owns any private C/vendor glue) — not general pass injection. See Plugin API — Render contract and the example MetalFX / FSR4 packages.
PreviousProject fileNext Hikari Plugin API

Documentation follows the current engine checkout.

Snapshot cc148c75Source docs/guides/plugins.md
On this pagePlugin surfacesLayoutManaging plugins in the editorCreate a plugin packageUse a plugin from game codeLifecycle hooksModule rolesEditor contributionsNative middleware variantsBuild and packed-editor workflowPackagingWhat plugins are not Back to top