The Vibes — the unofficial Laracon US Day 3 event. Early Bird tickets available until March 31!
Blog

🔌 Building Your First NativePHP Plugin: A Haptics Example

You've used NativePHP's built-in facades. Now it's time to build your own.

Plugins let you extend NativePHP with custom native functionality. Today we're building a Haptics plugin that goes beyond the basic Device::vibrate() — with patterns, intensities, and platform-specific feedback types.

In this post:

#Plugin Structure

A NativePHP plugin is just a Composer package:

Copied!
my-haptics-plugin/
├── composer.json
├── nativephp.json # The magic manifest
├── src/
│ ├── Haptics.php
│ ├── HapticsServiceProvider.php
│ └── Facades/Haptics.php
└── resources/
├── android/src/.../HapticsFunctions.kt
└── ios/Sources/HapticsFunctions.swift

#The Manifest File

The nativephp.json manifest wires everything together:

Copied!
{
"namespace": "Haptics",
"bridge_functions": [
{
"name": "Haptics.Impact",
"android": "com.yourname.haptics.HapticsFunctions.Impact",
"ios": "HapticsFunctions.Impact",
"description": "Trigger impact haptic feedback"
},
{
"name": "Haptics.Notification",
"android": "com.yourname.haptics.HapticsFunctions.Notification",
"ios": "HapticsFunctions.Notification",
"description": "Trigger notification haptic"
}
],
"android": {
"permissions": ["android.permission.VIBRATE"]
},
"ios": {
"min_version": "13.0"
}
}

A full list of all the available configuration options is contained in the Docs.

#PHP Layer

The PHP code in your plugin just needs to make use of the special nativephp_call function ("the god method"), which is available when running inside the context of a NativePHP shell app.

(As nativephp_call is not available in normal PHP installations, you should wrap it in a function_exists() check.)

This function receives the name of the native function you want to call (the name defined in your nativephp.json above) and its parameters as a JSON string as the second parameter:

Copied!
<?php
 
namespace YourName\Haptics;
 
class Haptics
{
public function impact(string $style = 'medium'): bool
{
if (function_exists('nativephp_call')) {
$result = nativephp_call('Haptics.Impact', json_encode([
'style' => $style,
]));
 
if ($result) {
$decoded = json_decode($result, true);
return $decoded['success'] ?? false;
}
}
return false;
}
 
public function notification(string $type = 'success'): bool
{
if (function_exists('nativephp_call')) {
$result = nativephp_call('Haptics.Notification', json_encode([
'type' => $type,
]));
 
if ($result) {
$decoded = json_decode($result, true);
return $decoded['success'] ?? false;
}
}
return false;
}
}

#Native Code

It's entirely up to you to decide how that function call from PHP is handled on the native side: you decide how the parameters get processed and what native functionality to execute.

When your plugin has been registered into your application, this native code gets compiled into your application and is then available to use.

#iOS (Swift)

Copied!
import UIKit
 
enum HapticsFunctions {
class Impact: BridgeFunction {
func execute(parameters: [String: Any]) throws -> [String: Any] {
let style = parameters["style"] as? String ?? "medium"
 
let feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle = {
switch style {
case "light": return .light
case "heavy": return .heavy
default: return .medium
}
}()
 
let generator = UIImpactFeedbackGenerator(style: feedbackStyle)
generator.impactOccurred()
 
return BridgeResponse.success(["success": true])
}
}
 
class Notification: BridgeFunction {
func execute(parameters: [String: Any]) throws -> [String: Any] {
let type = parameters["type"] as? String ?? "success"
 
let feedbackType: UINotificationFeedbackGenerator.FeedbackType = {
switch type {
case "warning": return .warning
case "error": return .error
default: return .success
}
}()
 
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(feedbackType)
 
return BridgeResponse.success(["success": true])
}
}
}

#Android (Kotlin)

Copied!
package com.yourname.haptics
 
import android.os.VibrationEffect
import android.os.Vibrator
import com.nativephp.mobile.bridge.BridgeFunction
import com.nativephp.mobile.bridge.BridgeResponse
 
object HapticsFunctions {
class Impact(private val context: Context) : BridgeFunction {
override fun execute(parameters: Map<String, Any>): Map<String, Any> {
val style = parameters["style"] as? String ?: "medium"
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
 
val amplitude = when (style) {
"light" -> 50
"heavy" -> 255
else -> 128
}
 
vibrator.vibrate(VibrationEffect.createOneShot(10, amplitude))
return BridgeResponse.success(mapOf("success" to true))
}
}
 
// Similare implementation for the 'notification' style feedback
}

#Using Your Plugin

Once installed, it works like any NativePHP facade. You can just call your PHP function from your controller, action or another part of your app and NativePHP takes care of calling the native code you created:

Copied!
use YourName\Haptics\Facades\Haptics;
 
// Impact feedback
Haptics::impact('heavy'); // Solid thud
Haptics::impact('light'); // Gentle tap
 
// Notification feedback
Haptics::notification('success'); // Quick double-tap
Haptics::notification('error'); // Longer error buzz

#In a Livewire Component

Copied!
public function submitForm()
{
$this->validate();
Haptics::notification('success');
$this->dispatch('form-submitted');
}

Congratulations! You just built your first native plugin in PHP. Your Laravel code can now trigger real haptic motors on real devices. 🎉

What will you build next?