Skip to main content
An Aether extension is a directory (or zip archive) containing a package.json that declares which entry files power which tiers. Aether reads this manifest at load time to discover entry points for the Pi agent layer, the Script Mod runtime, and the native DEX loader. Only the fields relevant to the tiers you use are required — unused tier fields are simply absent.

Full package.json example

The example below activates all three tiers from a single package:
package.json

Fields reference

name
string
required
An npm-style package name that identifies your extension. Use lowercase letters, numbers, and hyphens (for example, my-aether-mod or @myorg/chat-enhancer). Aether uses this name in the Extensions list and when generating stable extension IDs.
version
string
required
A semver version string (for example, 1.0.0). Increment this value when you publish updates so Aether and package managers can track changes.
pi.extensions
string[]
Array of paths to Pi extension entry files, relative to the package root. Each file is loaded by the Pi Coding Agent runtime. Export a factory function as default to register Pi commands, tools, and hooks. Omit this field if your extension does not target the Pi agent layer.
aether.extensions
string[]
Array of paths to Aether Script Mod entry files, relative to the package root. Each file is loaded by the Aether Script Mod runtime (API v2). Export your factory as activateAether or aether to register surfaces, components, pages, actions, and event handlers. The runtime also accepts a default export for entries explicitly listed here (useful for packages that target only this tier). Changes to these files hot-reload without restarting the app. Omit this field if your extension does not use the Script Mod API.
aether.native.classpath
string[]
Array of paths to .dex, .apk, or jar/zip files that contain a compiled classes.dex, relative to the package root. Aether passes these paths to DexClassLoader at process startup. A plain JVM .class jar is not sufficient — run Android D8/R8 on your compiled output first. Omit the entire aether.native object if your extension does not include a Native Mod.
aether.native.libraryPath
string[]
Array of paths to directories containing .so native libraries, relative to the package root. Aether adds these directories to the native library search path when loading the DEX classpath. Only required if your Native Mod uses JNI libraries.
aether.native.entrypoints
string[]
Array of fully-qualified Kotlin class names that implement AetherNativeMod. Aether instantiates each class via the DEX classloader and calls onLoad(context) during process startup. Requires aether.native.classpath to contain the compiled classes.

Script-only vs native-only packages

Script-only packages omit the aether.native object entirely. This is the most common extension format and supports full hot-reload:
package.json
Native-only zip packages can omit both the pi field and the extensions field under aether. Include only aether.native:
package.json

Installation location

Extension packages live under ~/.aether/extensions inside Aether’s managed Alpine filesystem. When you import a zip via Settings → Extensions → Import, Aether extracts the package into a subdirectory there. npm-based packages installed through Aether’s package management are also discovered from this root at load time.

Shared Pi + Aether entry point

When you share a single entry file between pi.extensions and aether.extensions, keep the Pi factory as the default export and export the Aether factory as activateAether or aether. The Script Mod runtime looks for activateAether or aether first; it falls back to default only for entries that are explicitly listed in aether.extensions (as opposed to implicitly discovered files).
index.ts
The defineAetherExtension helper provides TypeScript types for the aether argument and is a no-op at runtime — it simply returns the factory function you pass in. Import it from @aether/extension-api.