From nativephp-plugin-dev
This skill explains NativePHP plugin structure and configuration. Use when the user asks about "plugin structure", "nativephp.json", "plugin manifest", "composer.json setup", "service provider", "facade", "plugin directory layout", "bridge_functions array", "plugin permissions", "plugin dependencies", "plugin repositories", "custom maven repository", "plugin secrets", "environment variables", "placeholder substitution", "plugin hooks", "copy_assets", "features", "uses-feature", "meta_data", "background_modes", "entitlements", or how to organize a NativePHP plugin package.
npx claudepluginhub NativePHP/ClaudePlugins --plugin nativephp-plugin-devThis skill uses the workspace's default tool permissions.
NativePHP plugins are Composer packages that extend NativePHP with native iOS/Android functionality. This skill covers the complete plugin structure.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
NativePHP plugins are Composer packages that extend NativePHP with native iOS/Android functionality. This skill covers the complete plugin structure.
my-plugin/
├── composer.json # Composer package definition
├── nativephp.json # Plugin manifest (REQUIRED)
├── README.md # Plugin documentation
├── .gitignore
├── src/
│ ├── MyPluginServiceProvider.php # Laravel service provider
│ ├── MyPlugin.php # Main API class
│ ├── Facades/
│ │ └── MyPlugin.php # Laravel facade
│ ├── Events/ # Events dispatched from native
│ │ └── MyPluginCompleted.php
│ └── Commands/
│ └── CopyAssetsCommand.php # Lifecycle hook commands
├── resources/
│ ├── android/
│ │ └── MyPluginFunctions.kt # Kotlin bridge functions
│ ├── ios/
│ │ └── MyPluginFunctions.swift # Swift bridge functions
│ ├── js/
│ │ └── myPlugin.js # JavaScript bridge module
│ └── boost/
│ └── guidelines/
│ └── core.blade.php # Boost AI guidelines
└── tests/
├── Pest.php
└── PluginTest.php # Plugin validation tests
The Composer manifest must declare the package type as nativephp-plugin:
{
"name": "vendor/my-plugin",
"description": "A NativePHP plugin that does something awesome",
"type": "nativephp-plugin",
"license": "MIT",
"require": {
"php": "^8.1",
"nativephp/mobile": "^1.0"
},
"autoload": {
"psr-4": {
"Vendor\\MyPlugin\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Vendor\\MyPlugin\\MyPluginServiceProvider"
],
"aliases": {
"MyPlugin": "Vendor\\MyPlugin\\Facades\\MyPlugin"
}
}
}
}
| Field | Requirement |
|---|---|
type | MUST be "nativephp-plugin" |
require.nativephp/mobile | Required dependency |
extra.laravel.providers | Auto-register service provider |
extra.laravel.aliases | Auto-register facade |
This is the most important file in a NativePHP plugin. It tells NativePHP what native functionality the plugin provides.
Important: Package metadata (name, version, description, service_provider) comes from composer.json — don't duplicate it here. The manifest only contains native-specific configuration.
{
"namespace": "MyPlugin",
"bridge_functions": [
{
"name": "MyPlugin.Execute",
"android": "com.myvendor.plugins.myplugin.MyPluginFunctions.Execute",
"ios": "MyPluginFunctions.Execute",
"description": "Executes the main plugin action"
},
{
"name": "MyPlugin.GetStatus",
"android": "com.myvendor.plugins.myplugin.MyPluginFunctions.GetStatus",
"ios": "MyPluginFunctions.GetStatus",
"description": "Gets the current status"
}
],
"android": {
"permissions": [
"android.permission.CAMERA",
"android.permission.VIBRATE"
],
"dependencies": {
"implementation": [
"com.google.mlkit:barcode-scanning:17.2.0",
"androidx.camera:camera-core:1.3.0"
]
},
"activities": [
{
"name": ".ScannerActivity",
"theme": "@style/Theme.AppCompat.NoActionBar",
"screenOrientation": "portrait",
"exported": false
}
],
"services": [],
"receivers": [],
"providers": []
},
"ios": {
"info_plist": {
"NSCameraUsageDescription": "This plugin needs camera access to scan barcodes"
},
"dependencies": {
"swift_packages": [
{
"url": "https://github.com/example/package.git",
"version": "1.0.0"
}
],
"pods": [
"TensorFlowLiteSwift"
]
}
},
"assets": {
"android": {
"model.tflite": "assets/model.tflite"
},
"ios": {
"model.mlmodel": "Resources/model.mlmodel"
}
},
"events": [
"Vendor\\MyPlugin\\Events\\SomethingHappened",
"Vendor\\MyPlugin\\Events\\OperationCompleted"
],
"hooks": {
"copy_assets": "nativephp:my-plugin:copy-assets"
}
}
Maps PHP method calls to native implementations:
"bridge_functions": [
{
"name": "MyPlugin.Execute",
"android": "com.myvendor.plugins.myplugin.MyPluginFunctions.Execute",
"ios": "MyPluginFunctions.Execute",
"description": "What this function does"
}
]
name: The method name used in nativephp_call('MyPlugin.Execute', [])android: Full Kotlin class path (package + class name)ios: Swift class path (just the class names, enum.class format)description: Documentation for the functionNaming convention: Namespace.Action (e.g., Camera.Capture, Haptics.Vibrate)
All Android-specific configuration goes under the android key:
"android": {
"permissions": [
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO",
"android.permission.VIBRATE"
],
"features": [
{"name": "android.hardware.camera", "required": true},
{"name": "android.hardware.camera.autofocus", "required": false}
],
"repositories": [
{
"url": "https://api.mapbox.com/downloads/v2/releases/maven"
}
],
"dependencies": {
"implementation": [
"com.google.mlkit:barcode-scanning:17.2.0",
"androidx.camera:camera-camera2:1.3.0"
]
},
"meta_data": [
{
"name": "com.google.android.geo.API_KEY",
"value": "${GOOGLE_MAPS_API_KEY}"
}
],
"activities": [...],
"services": [...],
"receivers": [...],
"providers": [...]
}
permissions: Array of Android permission strings features: Hardware/software feature declarations (uses-feature) repositories: Custom Maven repositories (see below) dependencies: Gradle dependency strings (implementation, api, compileOnly, runtimeOnly) meta_data: Application-level meta-data entries for SDK configuration activities/services/receivers/providers: AndroidManifest.xml components
For SDKs not available on Maven Central or Google Maven (like Mapbox), add custom repositories:
"android": {
"repositories": [
{
"url": "https://api.mapbox.com/downloads/v2/releases/maven"
}
]
}
For private repositories that require authentication:
"android": {
"repositories": [
{
"url": "https://private.maven.example.com/releases",
"credentials": {
"username": "user",
"password": "${PRIVATE_SDK_TOKEN}"
}
}
]
}
The ${VAR} syntax references environment variables from the user's .env file.
All iOS-specific configuration goes under the ios key:
"ios": {
"info_plist": {
"NSCameraUsageDescription": "Explain why camera is needed",
"NSMicrophoneUsageDescription": "Explain why microphone is needed",
"MBXAccessToken": "${MAPBOX_ACCESS_TOKEN}"
},
"dependencies": {
"swift_packages": [
{
"url": "https://github.com/example/package.git",
"version": "1.0.0"
}
],
"pods": [
{"name": "TensorFlowLiteSwift", "version": "~> 2.0"}
]
},
"background_modes": ["audio", "fetch", "processing"],
"entitlements": {
"com.apple.developer.maps": true,
"com.apple.security.application-groups": ["group.com.example.shared"]
}
}
info_plist: Object mapping Info.plist keys to values (permissions, API tokens, config) dependencies: Swift Package URLs with versions, or CocoaPods names background_modes: UIBackgroundModes values (audio, fetch, processing, location, remote-notification, bluetooth-central, bluetooth-peripheral) entitlements: App entitlements for capabilities (Maps, App Groups, HealthKit, iCloud, etc.) init_function: Swift function to call during plugin initialization (see below)
Use ${ENV_VAR} placeholders for sensitive values like API tokens.
Plugins can specify an initialization function that runs during app startup. This is essential for plugins that need to:
NativePHPPluginRegistryNotificationCenter eventsiOS:
"ios": {
"init_function": "NativePHPMyPluginInit"
}
The function must be a @_cdecl exported C function in your Swift code:
@_cdecl("NativePHPMyPluginInit")
public func NativePHPMyPluginInit() {
// Initialize singletons
_ = MyPluginManager.shared
_ = MyPluginDelegate.shared
print("MyPlugin initialized")
}
Android:
"android": {
"init_function": "com.myvendor.plugins.myplugin.MyPluginInit"
}
The function must be a top-level function or object method in Kotlin:
fun MyPluginInit() {
// Initialize singletons, subscribe to lifecycle events
MyPluginDelegate.initialize()
}
When to use init_function:
NativePHPLifecycle events (Android) or NotificationCenter (iOS)NativePHPPluginRegistry for onAppLaunch callbacksEnable background execution capabilities:
"ios": {
"background_modes": ["audio", "fetch", "processing", "location"]
}
Common values:
audio — Audio playback or recordingfetch — Background fetchprocessing — Background processing taskslocation — Location updatesremote-notification — Push notification processingbluetooth-central — Bluetooth LE central modebluetooth-peripheral — Bluetooth LE peripheral modeThese are merged into UIBackgroundModes in Info.plist.
Configure app entitlements for capabilities:
"ios": {
"entitlements": {
"com.apple.developer.maps": true,
"com.apple.security.application-groups": ["group.com.example.shared"],
"com.apple.developer.associated-domains": ["applinks:example.com"],
"com.apple.developer.healthkit": true
}
}
Values can be:
true/false for simple capabilitiesEntitlements are written to NativePHP.entitlements. If the file doesn't exist, it's created automatically.
Plugins can declare required environment variables that users must provide in their .env file:
"secrets": {
"MAPBOX_ACCESS_TOKEN": {
"description": "Public access token for Mapbox SDK (starts with pk.)",
"required": true
},
"MY_API_KEY": {
"description": "API key for the service",
"required": false
}
}
Build-time validation: If a required secret is missing, the build fails with a helpful error message telling the user which secrets to add to their .env file.
Usage in manifest: Reference secrets using ${VAR} syntax in repositories, credentials, or assets:
"password": "${PRIVATE_SDK_TOKEN}" in repository credentials${MAPBOX_ACCESS_TOKEN} in asset files (see below)Static assets to copy during build are defined at the top level:
"assets": {
"android": {
"android/res/values/mapbox_token.xml": "res/values/mapbox_token.xml"
},
"ios": {
"model.mlmodel": "Resources/model.mlmodel"
}
}
Placeholder substitution: Asset files can contain ${VAR} placeholders that are automatically replaced with values from the user's .env file during the build.
Example XML asset template (resources/android/res/values/mapbox_token.xml):
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="mapbox_access_token" translatable="false"
tools:ignore="UnusedResources">${MAPBOX_ACCESS_TOKEN}</string>
</resources>
Supported file types for substitution: xml, json, txt, plist, strings, html, js, css, kt, swift, java
Use assets for small static files. Use the copy_assets hook for large files, ML models, or files that need processing.
Events that native code dispatches to PHP:
"events": [
"Vendor\\MyPlugin\\Events\\ScanCompleted",
"Vendor\\MyPlugin\\Events\\OperationFailed"
]
These are the fully-qualified PHP class names. Livewire components listen with:
#[On('native:Vendor\MyPlugin\Events\ScanCompleted')]
Lifecycle hooks for build-time operations:
"hooks": {
"copy_assets": "nativephp:my-plugin:copy-assets",
"pre_compile": "nativephp:my-plugin:pre-compile",
"post_compile": "nativephp:my-plugin:post-compile",
"post_build": "nativephp:my-plugin:post-build"
}
Each hook is an Artisan command signature.
Android activities, services, receivers, and providers are defined under android:
"android": {
"activities": [
{
"name": ".MyActivity",
"theme": "@style/Theme.AppCompat.NoActionBar",
"screenOrientation": "portrait",
"exported": false,
"launchMode": "singleTask",
"configChanges": "orientation|screenSize",
"intent-filters": [...]
}
],
"services": [
{
"name": ".MyService",
"exported": false,
"foregroundServiceType": "camera"
}
],
"receivers": [
{
"name": ".MyBroadcastReceiver",
"exported": true,
"intent-filters": [
{
"action": ["android.intent.action.BOOT_COMPLETED"]
}
]
}
],
"providers": []
}
Name resolution: Names starting with . are resolved from your plugin's package declaration (e.g., .MyActivity becomes com.myvendor.plugins.myplugin.MyActivity if your Kotlin files declare package com.myvendor.plugins.myplugin)
The service provider registers your plugin with Laravel:
<?php
namespace Vendor\MyPlugin;
use Illuminate\Support\ServiceProvider;
class MyPluginServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(MyPlugin::class, function ($app) {
return new MyPlugin();
});
}
public function boot(): void
{
// Register Artisan commands
if ($this->app->runningInConsole()) {
$this->commands([
Commands\CopyAssetsCommand::class,
]);
}
// Publish config (optional)
$this->publishes([
__DIR__.'/../config/my-plugin.php' => config_path('my-plugin.php'),
], 'my-plugin-config');
}
}
Provides a clean API for users:
<?php
namespace Vendor\MyPlugin\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @method static array execute(string $param)
* @method static array getStatus()
*
* @see \Vendor\MyPlugin\MyPlugin
*/
class MyPlugin extends Facade
{
protected static function getFacadeAccessor(): string
{
return \Vendor\MyPlugin\MyPlugin::class;
}
}
The implementation that calls native code:
<?php
namespace Vendor\MyPlugin;
class MyPlugin
{
public function execute(string $param): void
{
if (function_exists('nativephp_call')) {
nativephp_call('MyPlugin.Execute', json_encode([
'param' => $param,
]));
}
}
public function getStatus(): void
{
if (function_exists('nativephp_call')) {
nativephp_call('MyPlugin.GetStatus', '{}');
}
}
}
Simple POJOs for native-to-PHP events:
<?php
namespace Vendor\MyPlugin\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ScanCompleted
{
use Dispatchable, SerializesModels;
public function __construct(
public string $result,
public string $format,
public ?string $id = null
) {}
}
Important: Events do NOT use ShouldBroadcast or broadcasting channels. They're dispatched via JavaScript injection.
For build-time operations like copying ML models:
<?php
namespace Vendor\MyPlugin\Commands;
use Native\Mobile\Commands\NativePluginHookCommand;
class CopyAssetsCommand extends NativePluginHookCommand
{
protected $signature = 'nativephp:my-plugin:copy-assets';
protected $description = 'Copy plugin assets to native projects';
public function handle(): int
{
if ($this->isAndroid()) {
$this->copyToAndroidAssets(
'model.tflite',
'model.tflite'
);
}
if ($this->isIos()) {
$this->copyToIosBundle(
'model.mlmodel',
'model.mlmodel'
);
}
return self::SUCCESS;
}
}
| Method | Purpose |
|---|---|
$this->isAndroid() | Check if building for Android |
$this->isIos() | Check if building for iOS |
$this->copyToAndroidAssets($src, $dest) | Copy to Android assets |
$this->copyToIosBundle($src, $dest) | Copy to iOS bundle |
Place Kotlin files directly in resources/android/:
resources/android/
└── MyPluginFunctions.kt
Or with subdirectories for additional files:
resources/android/
├── MyPluginFunctions.kt
└── activities/
└── ScannerActivity.kt
Package naming: Use com.{vendor}.plugins.{pluginname} format:
package com.myvendor.plugins.myplugin
Note: The nested resources/android/src/ structure is also supported for backward compatibility.
Place Swift files directly in resources/ios/:
resources/ios/
└── MyPluginFunctions.swift
Or with subdirectories for additional files:
resources/ios/
├── MyPluginFunctions.swift
└── ViewControllers/
└── ScannerViewController.swift
Note: The nested resources/ios/Sources/ structure is also supported for backward compatibility.
After creating your plugin, users must install and explicitly register it.
composer require vendor/my-plugin
For local development, add a path repository to composer.json:
{
"repositories": [
{
"type": "path",
"url": "./packages/my-plugin"
}
]
}
php artisan vendor:publish --tag=nativephp-plugins-provider
This creates app/Providers/NativePluginsServiceProvider.php.
php artisan native:plugin:register vendor/my-plugin
This automatically adds the plugin's service provider to your plugins() array:
public function plugins(): array
{
return [
\Vendor\MyPlugin\MyPluginServiceProvider::class,
];
}
# Show registered plugins
php artisan native:plugin:list
# Show all installed plugins (including unregistered)
php artisan native:plugin:list --all
This is a security measure. It prevents transitive dependencies from automatically registering plugins without user consent. Only plugins explicitly listed in the provider are compiled into native builds.
To unregister a plugin from the app (but keep it installed):
php artisan native:plugin:register vendor/my-plugin --remove
To completely uninstall a plugin (unregister + remove code + composer remove):
php artisan native:plugin:uninstall vendor/my-plugin
Once registered, NativePHP automatically:
nativephp-plugin typenativephp.jsonMyPlugin.Scan not M.S