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", or how to organize a NativePHP plugin package.
/plugin marketplace add NativePHP/ClaudePlugins/plugin install nativephp-plugin-dev@nativephp-mobile-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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"
],
"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"
]
},
"activities": [...],
"services": [...],
"receivers": [...],
"providers": [...],
}
permissions: Array of Android permission strings repositories: Custom Maven repositories (see below) dependencies: Gradle dependency strings (implementation, api, compileOnly, runtimeOnly) 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": [
"TensorFlowLiteSwift"
]
}
}
info_plist: Object mapping Info.plist keys to values (permissions, API tokens, config) dependencies: Swift Package URLs with versions, or CocoaPods names
Use ${ENV_VAR} placeholders for sensitive values like API tokens.
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.SThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.