Skip to content
hikari
RenderingEditorAIToolchainDocumentation
Download

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsDownloadLicenseBack to top
Documentation / Guides
Browse docs
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
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 governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
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 governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari 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.

This guide covers source composition, manifests, and packaging. For editor scene APIs, render extensions, and gameplay contracts, see the Plugin API guide.

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). See the Plugin API guide for supported extension points.

DomainConcernWhereWhat you get
—Enable / composethis guidemanifests, roles, packaging
GameplayRuntime APIshikari_gamesame as game code
GameplayPackage settingshi.settings + Plugin APIconfigs/plugin_settings.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

Each surface stays a named, typed seam. There is no unified hi.plugin API, and new extension points are added as rows of this table rather than folded into one.

Layout

Project:

text
hikari-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: hikari-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): hikari-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/../hikari-games/example" --type=dynamic --config=debug

Editor with project:

bash
bin/kaji/kaji editor --workspace="$PWD" --project="$PWD/../hikari-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 --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 and the example MetalFX / FSR4 packages.
PreviousProject fileNext Hikari Plugin API

Documentation follows the current engine checkout.

Snapshot 86f12d68Source 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