Clash Nyanpasu Profile Enhancement System: JavaScript and Lua Scripting Guide
Clash Nyanpasu’s profile enhancement system allows users to transform Clash configurations on-the-fly using JavaScript or Lua scripts that execute before the core consumes the YAML.
The profile enhancement system in Clash Nyanpasu bridges the gap between static configuration files and dynamic runtime needs. By leveraging embedded JavaScript and Lua interpreters, users can programmatically modify, augment, or sanitize their Clash configs without leaving the GUI. This article examines the architecture, implementation, and practical usage of the profile enhancement system based on the libnyanpasu/clash-nyanpasu source code.
How the Profile Enhancement System Works
The system operates through three distinct layers: data model definitions, an execution engine that chains scripts together, and a user interface for managing enhancements.
Profile Data Model and Script Types
At the storage layer, a profile is represented as a YAML node. When the profile type is script, the node must include a script_type field specifying either javascript or lua.
In backend/tauri/src/config/profile/item/shared.rs, the Rust enum ProfileItemType::Script(ScriptType) handles serialization. The ScriptType enum (defined in backend/tauri/src/enhance/chain.rs at lines 34-41) explicitly distinguishes between the two languages:
pub enum ScriptType {
JavaScript,
Lua,
}
Built-in Enhancement Chain
Clash Nyanpasu ships with several built-in scripts—such as meta_guard.js, config_fixer.js, and clash_rs_comp.lua—that ensure compatibility across different Clash cores.
The ChainItem::builtin() function in backend/tauri/src/enhance/chain.rs (lines 45-75) constructs a vector of (BitFlags<ClashCore>, ChainItem) pairs. Each item wraps a script using either ChainTypeWrapper::new_js or ChainTypeWrapper::new_lua, depending on the file extension. When a profile loads, the runtime selects the appropriate chain based on the active core (Mihomo, Clash Rs, etc.) and executes scripts sequentially, mutating the configuration object in memory.
User-Defined Scripts
Users can inject custom logic through the frontend interface. The Import Chain Profile modal (frontend/nyanpasu/src/pages/(main)/main/profiles/$type/_modules/chain-profile-import.tsx) presents a form schema that includes a script_type field validated as z.literal('javascript').or(z.literal('lua')).nullable().
Upon submission, the profile is stored via useProfile().create. Editing an existing script opens script-dialog.tsx, which renders a Monaco-based code editor. The dialog switches between ProfileTemplate.javascript and ProfileTemplate.luascript templates (defined in frontend/interface/src/template/index.ts) based on the profile's script_type.
Runtime Execution
When Clash starts, the frontend transmits the full profile list to the backend through the Tauri bridge. The backend extracts all items where type == "script", converts each into a ChainItem via ChainItem::to_script(uid, …), and appends them to the built-in chain.
JavaScript snippets execute under QuickJS (via the javascriptcore-rs crate), while Lua snippets run under rlua (exposed as ChainTypeWrapper::new_lua). The combined chain transforms the raw YAML before the Clash core ever reads it.
Practical Usage Examples
Adding a JavaScript Enhancement via the UI
Navigate to the Profiles page and click the Import Chain Profile button. In the modal:
- Set Script Type to
JavaScript. - Enter a name and optional description.
- Submit the form.
The system stores the profile as:
{
"uid": "auto-generated-uuid",
"type": "script",
"script_type": "javascript",
"name": "My JS Enhancer",
"file": null,
"desc": "Custom DNS routing logic"
}
Editing the profile opens the Monaco editor pre-filled with the JavaScript template.
Defining a Lua Script Manually
You can also author scripts directly in YAML files. Create ~/.config/clash/nyanpasu/profiles/strip-fields.yaml:
type: script
script_type: lua
name: "Strip unsupported fields"
file: |
-- Lua code executed before the core reads the config
function enhance(config)
config["port"] = nil -- remove port key for Clash Rs compatibility
return config
end
When selected, the backend loads script_type = lua, wraps it with ChainTypeWrapper::new_lua, and injects it into the execution chain.
Using the Built-in Chain Programmatically
For developers extending the backend, you can construct script items manually in Rust:
use crate::enhance::{ChainItem, ChainTypeWrapper};
let custom_js = ChainItem::to_script(
"my_custom_js",
ChainTypeWrapper::new_js(
"function enhance(cfg) { cfg.dns = { enable: true }; return cfg; }".to_string()
),
);
let mut full_chain = ChainItem::builtin();
full_chain.push((ClashCore::Mihomo, custom_js));
This appends your JavaScript enhancer to the default built-in chain before the core launches.
Key Source Files
Summary
- Clash Nyanpasu’s profile enhancement system enables dynamic configuration transformation through JavaScript and Lua scripts executed before the core reads the YAML.
- Script types are defined in the data model (
ProfileItemType::Script) with explicitscript_typefields distinguishing JavaScript from Lua. - Execution engine chains built-in scripts (
meta_guard.js,clash_rs_comp.lua) with user-defined snippets usingChainItem::builtin()andChainTypeWrapperfor QuickJS and rlua runtimes. - Frontend interface provides Monaco-based editing through
script-dialog.tsxand import workflows viachain-profile-import.tsx, supporting both languages transparently.
Frequently Asked Questions
What languages does the profile enhancement system support?
The system supports JavaScript and Lua. These are defined in the ScriptType enum in backend/tauri/src/enhance/chain.rs, and the frontend enforces these values through Zod schema validation in the import modal.
How are JavaScript and Lua scripts executed in Clash Nyanpasu?
JavaScript snippets run under QuickJS via the javascriptcore-rs crate, while Lua snippets execute under rlua. The backend wraps each script in a ChainTypeWrapper (new_js or new_lua) and processes them sequentially through the enhancement chain before passing the final YAML to the Clash core.
Can I combine built-in and custom scripts in the same profile?
Yes. The system appends user-defined scripts to the built-in chain at runtime. When you create a script profile through the UI or manually define one in YAML, the backend calls ChainItem::to_script() and pushes it into the vector returned by ChainItem::builtin(), ensuring both built-in fixes and your custom logic execute in sequence.
Where are script-based profiles stored and managed?
Script profiles are stored as YAML files in the Nyanpasu configuration directory (typically ~/.config/clash/nyanpasu/profiles/). Each file contains the type: script and script_type fields. The frontend manages these through React hooks like useProfile().create, while the backend deserializes them via ProfileItemType::Script in backend/tauri/src/config/profile/item/shared.rs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →