Skip to content

Plugin SDK

PAM Native plugins are ordinary Composer packages. One package can include:

  • PHP providers, tags, components, themes, and utility tokens;
  • Android native modules;
  • Android native view factories;
  • Kotlin or Java source;
  • resources, assets, and manifests;
  • Maven dependencies and local AARs;
  • JNI libraries; and
  • R8 consumer rules.

Discovery and autolinking happen at build time. Production frames do not scan classpaths or parse plugin descriptors.

Terminal window
pam composer require vendor/maps-plugin
pam mobile plugin:doctor
pam mobile plugin:list
pam mobile codegen

prepare, build, run, and dev also run code generation. The resolved graph is written to .pam-native/plugins.lock.json with package versions and descriptor SHA-256 hashes.

Commit composer.lock. Treat the PAM lock as generated build evidence.

{
"name": "vendor/maps-plugin",
"type": "pam-native-plugin",
"autoload": {
"psr-4": {
"Vendor\\Maps\\": "src/"
}
},
"extra": {
"pam-native": {
"plugin": "pam-native.plugin.json"
}
}
}
{
"version": 1,
"protocol": 1,
"pamNative": {
"minimum": "0.2.0",
"maximumExclusive": "0.3.0"
},
"php": {
"provider": "Vendor\\Maps\\MapsPluginProvider"
},
"android": {
"namespace": "com.vendor.pam.maps",
"minSdk": 26,
"permissions": [
"android.permission.ACCESS_FINE_LOCATION"
],
"dependencies": [
"com.vendor:maps-android:4.2.0"
]
},
"modules": [
{
"name": "maps.geocoder",
"class": "com.vendor.pam.maps.GeocoderModule"
}
],
"views": [
{
"name": "maps.route",
"class": "com.vendor.pam.maps.RouteMapFactory"
}
]
}

The complete schema also supports HTTPS Maven repositories, local AARs, source/resource/asset/JNI directories, a manifest, and consumer rules.

PAM canonicalizes package-relative paths and rejects:

  • traversal and symlink escapes;
  • oversized descriptors;
  • duplicate binding names;
  • incompatible SDK or protocol ranges;
  • non-HTTPS Maven repositories;
  • invalid coordinates; and
  • a plugin minSdk above the application minimum.

PAM deliberately does not execute arbitrary package-provided Gradle scripts. The declared integration surface remains auditable.

Providers register in package-name order before the first render:

final class MapsPluginProvider implements PluginProvider
{
public function register(): void
{
TemplateRegistry::component(
'RouteMap',
static fn (
array $props,
array $_children,
?object $_scope,
): CustomView => CustomView::make(
'maps.route',
[
'routeId' => (string) (
$props['routeId'] ?? ''
),
],
),
);
}
public function boot(): void
{
}
}

Providers are optional. A plugin may be PHP-only, Android-only, or combined.

Native modules receive an Android Context and compile against the stable :plugin-api library. PHP calls a typed scalar map with NativeModules::call() or a package-owned binary payload with callRaw().

Slow module work must leave the UI thread. Completion returns success or failure through the bounded callback contract.

Put a capability in PAM Native core only when it is platform-generic, has a stable cross-application contract, and can be supported as part of the renderer compatibility matrix. Timers, files, HTTP, permissions, storage, lifecycle, and primitive media behavior meet that bar.

Keep product protocols and optional heavy integrations in plugins. Examples include an application’s signed-upload workflow, Pusher-compatible channel authentication, WebRTC signaling rules, custom camera pipelines, or a domain-specific background worker. A plugin may still be public and reusable; remaining outside the core prevents every PAM application from inheriting its dependencies and product assumptions.

Before adding native code to a plugin, check the core system APIs. For example, delayed work should use Pam\Native\System\Timers rather than introducing a second Android Handler contract in a media or WebRTC package.

  • Descriptor versions, protocols, statuses, and operation kinds are sequential integers.
  • Packages declare a closed SDK range with minimum and maximumExclusive.
  • Binding names are globally unique.
  • A breaking plugin API change requires a protocol or compatibility-range change.
  • Test the lowest and newest SDK versions claimed.