This article will take you from zero to mastering the core knowledge of Magisk module development, and through practical case studies, you will learn how to write your own modules.
Table of Contents
- 1. What is a Magisk Module
- 2. How Magisk Modules Work
- 3. Basic Structure of a Module
- 4. Detailed Explanation of Core Files
- 5. Practical Case Study: WeChat Auto-Launch Module
- 6. Advanced Techniques
- 7. Debugging and Publishing
- 8. Frequently Asked Questions
1. What is a Magisk Module
1.1 Introduction to Magisk
Magisk is the most popular root tool on the Android system, and its core advantages are:
- Systemless modification: does not directly modify the system partition
- Modular management: implements various functions through modules
- SafetyNet compatibility: can pass Google security certification
- OTA friendly: system updates do not affect root
1.2 Application Scenarios of Magisk Modules
Magisk modules can achieve various powerful functions:
- System optimization: modify system parameters, adjust performance
- Function enhancement: add system-level features (such as automation scripts)
- Application disguise: hide root, modify device information
- File replacement: replace system files (fonts, animations, ringtones, etc.)
- Service injection: start custom services at boot
2. How Magisk Modules Work
2.1 Core Mechanism: MagiskMount
Magisk uses mount technology to achieve systemless modification:
Real system partition (/system)
↓
Magisk intercepts
↓
Module file overlay (/data/adb/modules/*)
↓
System as seen by the user
When you create <span>/system/app/MyApp.apk</span> in the module, Magisk will “overlay” it on top of the real <span>/system</span>, and both the application and the system will think this file exists, but the actual system partition has not been modified.
2.2 Module Loading Timing
Magisk has three key stages during the boot process:
| Stage | Timing | Corresponding Script | Characteristics |
|---|---|---|---|
| post-fs | After mounting the system partition | <span>post-fs.sh</span> |
Executed first, SELinux not fully loaded |
| post-fs-data | After mounting the data partition | <span>post-fs-data.sh</span> |
Can access /data, user data not decrypted |
| late_start | After the system has fully booted | <span>service.sh</span> |
Executed last, system fully ready |
Selection Recommendations:
- Modify system files →
<span>post-fs-data.sh</span> - Start background services →
<span>service.sh</span>(recommended) - Emergency boot repair →
<span>post-fs.sh</span>
3. Basic Structure of a Module
3.1 Minimum Module Structure
A basic Magisk module only requires two files:
my-module/
├── module.prop # Module information (required)
└── service.sh # Startup script (optional)
3.2 Complete Module Structure
A fully functional module usually contains:
my-module/
├── module.prop # Module metadata (required)
├── install.sh # Installation script (optional)
├── uninstall.sh # Uninstallation script (optional)
├── service.sh # late_start startup script
├── post-fs-data.sh # post-fs-data startup script
├── sepolicy.rule # SELinux policy rules
├── system.prop # System property modifications
├── system/ # System file replacement directory
│ ├── app/
│ ├── framework/
│ └── etc/
├── scripts/ # Custom scripts directory
└── README.md # Documentation
4. Detailed Explanation of Core Files
4.1 module.prop – Module Configuration
This is the “identity card” of the module and must include the following fields:
id=my-awesome-module
name=My Awesome Module
version=v1.0.0
versionCode=100
author=YourName
description=This is a description of a demo Magisk module
Field Descriptions:
| Field | Description | Requirements |
|---|---|---|
<span>id</span> |
Unique identifier for the module | Can only contain letters, numbers, underscores, dots, and hyphens |
<span>name</span> |
Display name | Displayed in Magisk Manager |
<span>version</span> |
Version number (for display) | Any string |
<span>versionCode</span> |
Version number (for comparison) | Integer, used for update detection |
<span>author</span> |
Author name | Any string |
<span>description</span> |
Module description | Briefly describes the module’s functionality |
Optional Fields:
# Update detection
updateJson=https://example.com/update.json
# Minimum Magisk version requirement
minMagisk=20000
# Minimum Android version requirement (API Level)
minApi=21
maxApi=33
4.2 service.sh – Startup Service Script
This is the most commonly used startup script, executed after the system has fully booted:
#!/system/bin/sh
# Get module directory path
MODDIR=${0%/*}
# Wait for the system to finish booting
while [ "
$(getprop sys.boot_completed)" != "1" ]; do
sleep 2
done
# Wait for user unlock (optional)
while [ "
$(getprop sys.boot_completed.main)" != "1" ]; do
sleep 1
done
# Your custom logic
echo "Module startup complete" > /data/local/tmp/module.log
# Start background service
"$MODDIR/scripts/my_service.sh" &
Key Points:
- Shebang must be
<span>#!/system/bin/sh</span>(Android system shell) <span>$MODDIR</span>variable: module installation directory, usually<span>/data/adb/modules/moduleID</span>- Wait for boot completion: ensure system services are ready
- Run in the background: remember to add
<span>&</span>for long-running tasks
4.3 post-fs-data.sh – Early Startup Script
Executed immediately after the data partition is mounted, suitable for file operations:
#!/system/bin/sh
MODDIR=${0%/*}
# Create temporary file
directory -p /data/local/tmp/mymodule
# Modify system configuration
echo "net.ipv4.tcp_fastopen=3" >> /data/local/tmp/sysctl.conf
# Note: User data is not decrypted at this time, do not access /data/data/
4.4 install.sh – Installation Script
Executed during module installation/update, can perform environment checks and file preparations:
#!/system/bin/sh
# Environment variables provided by Magisk
# $MAGISK_VER_CODE - Magisk version number
# $BOOTMODE - Whether installed in boot mode
# $MODPATH - Module installation path
# Check device architecture
ABI=$(getprop ro.product.cpu.abi)
ui_print "Detected device architecture: $ABI"
# Copy different binaries based on architecture
case $ABI in
arm64-v8a)
cp -f "$MODPATH/bin/arm64/mybinary" "$MODPATH/mybinary"
;;
armeabi-v7a)
cp -f "$MODPATH/bin/arm/mybinary" "$MODPATH/mybinary"
;;
*)
ui_print "Unsupported architecture: $ABI"
exit 1
;;
esac
# Set permissions
chmod 0755 "$MODPATH/mybinary"
set_perm_recursive "$MODPATH" 0 0 0755 0644
ui_print "Installation complete!"
Common Functions:
<span>ui_print <msg></span>– Display information in the installation interface<span>abort <msg></span>– Abort installation and display error message<span>set_perm <file> <owner> <group> <permission></span>– Set file permissions
4.5 uninstall.sh – Uninstallation Script
Executed to clean up during module uninstallation:
#!/system/bin/sh
# Clean up residual files
rm -rf /data/local/tmp/mymodule
# Clean up temporary logs
rm -f /data/local/tmp/module.log
# Kill background processes (if any)
killall -9 my_service.sh
4.6 system.prop – System Property Modifications
Modify system properties (equivalent to build.prop):
# Modify device model
ro.product.model=Pixel 8 Pro
# Enable debug mode
persist.sys.usb.config=adb
# Modify DNS
net.dns1=8.8.8.8
net.dns2=8.8.4.4
# Performance optimization
debug.sf.hw=1
ro.sf.lcd_density=480
Note:<span>ro.*</span> prefixed properties are read-only and only take effect at boot.
4.7 sepolicy.rule – SELinux Policies
Define SELinux permission rules:
# Allow shell to execute system_file
allow shell system_file execute
# Allow system_app to access the network
allow system_app net_domain:rawip_socket create_socket_perms
# Allow reading /data/data/
allow system_server app_data_file:dir r_dir_perms
Syntax:
allow <source_domain> <target_domain>:<class> <permission>
deny <source_domain> <target_domain>:<class> <permission>
5. Practical Case Study: WeChat Auto-Launch Module
Now we will develop a module that automatically launches WeChat every 10 minutes through a practical case study.
5.1 Requirement Analysis
Functional Goals:
- Automatically run after system boot
- Launch WeChat once every 10 minutes
- Support dynamic interval adjustment
- Complete logging
Technical Solution:
- Use
<span>service.sh</span>to execute after the system has fully booted - Use
<span>monkey</span>/<span>am start</span>commands to launch the application - Use
<span>setprop</span>/<span>getprop</span>for dynamic configuration - Run in a background loop
5.2 Module Structure Design
wechat-auto-launch/
├── module.prop
├── service.sh
└── launch_wechat.sh
5.3 Code Implementation
module.prop
id=wechat-auto-launch
name=WeChat Auto-Launch
version=1.0.0
versionCode=100
author=YourName
description=Automatically launches WeChat in the background every 10 minutes to keep the process active for receiving messages
minMagisk=20000
minApi=21
service.sh
#!/system/bin/sh
MODDIR=${0%/*}
# Ensure the script is executable
chmod 0755 "$MODDIR/launch_wechat.sh"
# Wait for the system to finish booting
while [ "
$(getprop sys.boot_completed)" != "1" ]; do
sleep 2
done
# Wait a few more seconds to ensure PackageManager is ready
sleep 8
# Log startup
echo "[$(date)] WeChat auto-launch module has started" > /data/local/tmp/wechat_auto.log
# Start background loop mode
"$MODDIR/launch_wechat.sh" loop >> /data/local/tmp/wechat_auto.log 2>&1 &
launch_wechat.sh
#!/system/bin/sh
# WeChat package name
WECHAT_PKG="com.tencent.mm"
DEFAULT_INTERVAL=600 # Default 10 minutes
# Read dynamic interval
read_interval_seconds() {
PROP=$(getprop notif.wechat.interval 2>/dev/null)
if echo "$PROP" | grep -Eq '^[0-9]+$'; then
VAL=$PROP
[ "$VAL" -lt 60 ] && VAL=60
[ "$VAL" -gt 86400 ] && VAL=86400
echo "$VAL"
return 0
fi
echo $DEFAULT_INTERVAL
}
# Log function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
[ -x /system/bin/log ] && /system/bin/log -t wechat-auto "$*"
}
# Check if the application is installed
is_installed() {
pm path "$1" >/dev/null 2>&1
}
# Launch WeChat
launch_wechat() {
if ! is_installed "$WECHAT_PKG"; then
log "WeChat is not installed, exiting script"
exit 1
fi
# Prefer using monkey
if command -v monkey >/dev/null 2>&1; then
monkey -p "$WECHAT_PKG" -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 &&
log "✓ WeChat launched [monkey]" && return 0
fi
# Fallback: am start
am start -W -a android.intent.action.MAIN \
-c android.intent.category.LAUNCHER \
-p "$WECHAT_PKG" >/dev/null 2>&1 &&
log "✓ WeChat launched [am]" && return 0
log "✗ Failed to launch WeChat"
}
# Loop mode
loop_mode() {
log "========== WeChat auto-launch module started =========="
log "Default interval: $DEFAULT_INTERVAL seconds"
log "Dynamic configuration: setprop notif.wechat.interval <seconds>"
log "=========================================="
ROUND=0
while true; do
ROUND=$((ROUND + 1))
log "---------- Round $ROUND launch ----------"
launch_wechat
SEC=$(read_interval_seconds)
log "Completed round $ROUND, sleeping ${SEC} seconds"
sleep "$SEC"
done
}
# Main entry
case "$1" in
loop) loop_mode ;;
*) echo "Usage: $0 loop" ; exit 1 ;;
esac
5.4 Packaging and Installation
Method 1: Manual Packaging (Recommended)
# 1. Create module directory
mkdir -p wechat-auto-launch
# 2. Create the above three files
# 3. Set permissions
chmod 0644 wechat-auto-launch/module.prop
chmod 0755 wechat-auto-launch/*.sh
# 4. Package into zip
cd wechat-auto-launch
zip -r ../wechat-auto-launch.zip .
# 5. Flash the zip package in Magisk Manager
# 6. Reboot the phone
Method 2: Command Line Installation (for debugging)
# 1. Directly create module directory
adb shell su -c 'mkdir -p /data/adb/modules/wechat-auto-launch'
# 2. Push files
adb push module.prop /data/adb/modules/wechat-auto-launch/
adb push service.sh /data/adb/modules/wechat-auto-launch/
adb push launch_wechat.sh /data/adb/modules/wechat-auto-launch/
# 3. Set permissions
adb shell su -c 'chmod -R 0755 /data/adb/modules/wechat-auto-launch/*.sh'
# 4. Reboot the phone
adb reboot
5.5 Usage and Adjustment
View Logs
adb shell su -c 'cat /data/local/tmp/wechat_auto.log'
adb shell su -c 'tail -f /data/local/tmp/wechat_auto.log'
Dynamic Interval Adjustment
# Change to 5 minutes (300 seconds)
adb shell su -c 'setprop notif.wechat.interval 300'
# Change to 30 minutes (1800 seconds)
adb shell su -c 'setprop notif.wechat.interval 1800'
Manual Testing
# Manually run the script (foreground testing)
adb shell su -c 'sh /data/adb/modules/wechat-auto-launch/launch_wechat.sh loop'
6. Advanced Techniques
6.1 Replacing System Files
To replace <span>/system/app/Calculator/Calculator.apk</span>:
my-module/
└── system/
└── app/
└── Calculator/
└── Calculator.apk
Magisk will automatically mount the <span>system/</span> directory in the module to the real <span>/system/</span>.
6.2 Adding New System Applications
my-module/
└── system/
└── app/
└── MyApp/
└── MyApp.apk
After rebooting, <span>MyApp.apk</span> will be installed as a system application.
6.3 Modifying System Library Files
Replace native libraries:
my-module/
└── system/
└── lib64/
└── libmylibrary.so
6.4 Using Busybox
Many scripts rely on the complete shell toolset provided by Busybox:
#!/system/bin/sh
# Use the Busybox that comes with Magisk
export PATH="/data/adb/magisk:$PATH"
# Now you can use the complete GNU tools
find /data/local/tmp -name "*.log" -mtime +7 -delete
6.5 Inter-Module Communication
Communicate between modules through system properties or files:
# Module A writes status
setprop module.a.status "ready"
# Module B reads status
STATUS=$(getprop module.a.status)
if [ "$STATUS" = "ready" ]; then
echo "Module A is ready"
fi
6.6 Implementing Module Update Detection
Add to <span>module.prop</span>:
updateJson=https://raw.githubusercontent.com/yourname/module/master/update.json
Create <span>update.json</span>:
{
"version": "1.1.0",
"versionCode": 110,
"zipUrl": "https://github.com/yourname/module/releases/download/v1.1.0/module.zip",
"changelog": "https://github.com/yourname/module/releases/tag/v1.1.0"
}
6.7 Multi-Architecture Binary Support
# install.sh
ABI=$(getprop ro.product.cpu.abi)
case $ABI in
arm64-v8a)
cp "$MODPATH/bin/arm64/binary" "$MODPATH/binary"
;;
armeabi-v7a)
cp "$MODPATH/bin/arm/binary" "$MODPATH/binary"
;;
x86_64)
cp "$MODPATH/bin/x86_64/binary" "$MODPATH/binary"
;;
x86)
cp "$MODPATH/bin/x86/binary" "$MODPATH/binary"
;;
*)
abort "Unsupported architecture: $ABI"
;;
esac
chmod 0755 "$MODPATH/binary"
7. Debugging and Publishing
7.1 Debugging Techniques
Real-Time Log Viewing
# Real-time view system logs
adb logcat -s Magisk:* MagiskSU:* magisk:*
# Real-time view module logs
adb shell su -c 'tail -f /data/local/tmp/module.log'
Manually Run Scripts
# Test service.sh without rebooting
adb shell su -c 'sh /data/adb/modules/your-module/service.sh'
Check Module Status
# View installed modules
adb shell su -c 'ls /data/adb/modules/'
# Check if the module is enabled (if a disable file exists, it is disabled)
adb shell su -c 'ls /data/adb/modules/your-module/disable'
Disable Module (without Uninstalling)
# Create disable file
adb shell su -c 'touch /data/adb/modules/your-module/disable'
adb reboot
7.2 Common Error Troubleshooting
1. Module Not Effective
# Check if module.prop format is correct
adb shell su -c 'cat /data/adb/modules/your-module/module.prop'
# Check script permissions
adb shell su -c 'ls -l /data/adb/modules/your-module/'
2. Script Cannot Execute
# Check shebang
head -n1 /data/adb/modules/your-module/service.sh
# Must be #!/system/bin/sh, cannot be #!/bin/bash
3. SELinux Prevents Execution
# Temporarily disable SELinux for testing
adb shell su -c 'setenforce 0'
# View SELinux logs
adb shell su -c 'dmesg | grep avc'
7.3 Performance Optimization
Reduce Startup Delay
# Move time-consuming operations to the background
(
sleep 60
# Time-consuming operation
./long_running_task.sh
) &
Avoid CPU Usage from Infinite Loops
# Incorrect: occupies 100% CPU
while true; do
check_status
done
# Correct: reasonable sleep
while true; do
check_status
sleep 5
done
7.4 Preparation for Release
Packaging Specifications
# Standard directory structure
your-module/
├── META-INF/ # Flash package signature (optional)
│ └── com/
│ └── google/
│ └── android/
│ ├── update-binary
│ └── updater-script
├── module.prop
├── service.sh
├── README.md
└── changelog.md
# Packaging command
zip -r9 your-module-v1.0.zip your-module/
Release Checklist
- [ ]
<span>module.prop</span>information is complete - [ ]
<span>README.md</span>documentation is included - [ ]
<span>changelog.md</span>update log is included - [ ] All scripts have correct permissions set (0755)
- [ ] Tested on multiple devices
- [ ] Compatibility notes (Android version, Magisk version)
- [ ] List of known issues
Release Channels
- GitHub Releases: mainstream recommendation
- XDA Developers: international community
- Coolapk: domestic Android community
- Magisk Module Repository: Magisk Modules Repo
8. Frequently Asked Questions
Q1: What is the difference between modules and Xposed modules?
Magisk Modules:
- Based on filesystem mounting, modifying system files and behaviors
- Can be enabled/disabled without rebooting (except for some modifications)
- Suitable for system-level modifications, script automation
Xposed Modules:
- Based on hook technology, modifying application runtime behavior
- Requires reboot to take effect
- Suitable for application feature enhancement, interface modification
Both can be used together without conflict.
Q2: Why can’t my script execute in service.sh?
Common reasons:
- Incorrect Shebang: must be
<span>#!/system/bin/sh</span> - Insufficient permissions: script needs 0755 permissions
- Incorrect path: use
<span>$MODDIR</span>instead of hardcoded paths - Missing environment variables: Android shell environment is different from Linux
Q3: How to disable a module on specific devices?
# Detect device in install.sh
DEVICE=$(getprop ro.product.device)
if [ "$DEVICE" != "pixel" ]; then
abort "This module only supports Pixel devices"
fi
Q4: Will the module affect OTA updates?
The systemless feature of Magisk keeps the system partition intact, not affecting OTA updates. However, after OTA, you need to re-flash Magisk, and the module will automatically restore.
Q5: How to automatically disable specific applications on boot?
# service.sh
#!/system/bin/sh
# Wait for the system to be ready
while [ "
$(getprop sys.boot_completed)" != "1" ]; do
sleep 2
done
# Disable application
pm disable com.example.app
# Or freeze application (requires Magisk permissions)
pm disable-user --user 0 com.example.app
Q6: Can modules modify data of other applications?
Yes, but be aware of:
- SELinux restrictions: may require custom policies
- Application encryption: application data encryption in Android 10+
- Permission issues: even with root, respect file permissions
# Modify application SharedPreferences
sqlite3 /data/data/com.example.app/databases/config.db \
"UPDATE settings SET value='1' WHERE key='debug_mode';"
Q7: How to implement a configuration interface for the module?
Three options:
- System properties:
<span>setprop</span>/<span>getprop</span>(simplest) - Configuration files: create configuration files in
<span>/data/local/tmp/</span> - Standalone APP: develop a configuration application (most professional)
Q8: Will the module increase power consumption?
Depends on implementation:
- Background script loops: use
<span>sleep</span>reasonably to avoid spinning - File mounting: almost no additional power consumption
- Frequent operations: such as launching applications every second, will significantly increase power consumption
Conclusion
Through this article, you have mastered:
✅ The working principle of Magisk modules ✅ The file structure and core files of modules ✅ How to write startup scripts (service.sh) ✅ Practical development of an automation module ✅ Debugging, optimizing, and publishing modules
Learning Resources:
- Magisk Official Documentation
- Magisk Modules Repo
- XDA Developers Forum
Follow my public account for more valuable content 👇