# How to Extend KCL Configurations with Custom Functions or External Plugins

> Extend KCL configurations with custom functions or external plugins. Learn to import modules, write reusable code, and enable plugins for enhanced functionality.

- Repository: [The KCL Programming Language/kcl](https://github.com/kcl-lang/kcl)
- Tags: how-to-guide
- Published: 2026-03-05

---

**KCL configurations can be extended by importing built-in plugin modules that load native shared libraries via the `kcl_plugin.` prefix, or by writing reusable functions directly in `.k` files, with the runtime enabling plugins through the `load_plugins` flag and C-ABI entry points `kcl_plugin_init` and `kcl_plugin_invoke_json`.**

The KCL programming language, maintained in the `kcl-lang/kcl` repository, provides a robust extension system that allows developers to enhance configurations with custom logic. Whether you need to integrate external cryptographic libraries or reuse validation logic across projects, understanding how to extend KCL configurations with custom functions or external plugins unlocks the full potential of the language.

## Understanding KCL Extension Mechanisms

KCL supports two complementary approaches for extending configuration capabilities, each serving different use cases and implementation requirements.

### Built-in Plugin Modules (Native Shared Libraries)

The **plugin module** mechanism enables KCL to load native shared libraries at runtime through a C-ABI interface. This approach is implemented in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs) and allows developers to expose functions written in languages like Rust, C, or Go to KCL code. The system uses a global dispatcher stored in `PLUGIN_HANDLER_FN_PTR`, a `Mutex<Option<...>>` that holds the function pointer registered by `kcl_plugin_init`.

### Custom Functions Written in KCL

For logic that does not require native performance or external system access, developers can define **custom functions** directly in `.k` files. These functions are parsed and evaluated by the KCL runtime like any other code, residing in the project's source tree rather than external shared libraries. The function evaluation logic is handled in [`crates/evaluator/src/func.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/func.rs), where the runtime manages function scopes and argument passing.

## Architecture of the KCL Plugin System

The plugin architecture spans multiple crates in the KCL codebase, coordinating compile-time resolution with runtime execution.

### Parser and Resolver Integration

When a KCL program is parsed, the `load_plugins` flag (defaulting to `false`) is stored in `ParserOptions` within [`crates/parser/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/parser/src/lib.rs) (lines 265-283). If a file imports a module whose name starts with `kcl_plugin.`, the parser checks this flag; otherwise, it aborts with an error.

The semantic resolver treats any import beginning with `kcl_plugin.` as a plugin package, using the constant `PLUGIN_MODULE_PREFIX` defined in [`crates/sema/src/plugin/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/plugin/mod.rs). This prefix convention ensures that the compiler can distinguish between standard library modules and external plugin modules.

### Runtime Plugin Handler

The shared library must expose two specific C-ABI entry points:

1. **`kcl_plugin_init`** – Registers the function dispatcher, a pointer to a function that receives `method`, `args_json`, and `kwargs_json` parameters.
2. **`kcl_plugin_invoke_json`** – Called by the KCL runtime to forward method calls to the dispatcher, implemented in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs) (lines 11-20).

### Invocation Flow

When KCL evaluates `import kcl_plugin.my_pkg` and subsequently calls `kcl_plugin.my_pkg.my_func(args…)`, the evaluator performs a two-stage lookup:

1. **Direct registration check** – The evaluator looks up `my_func` in the runtime context's `plugin_functions` map (see [`crates/api/src/kcl.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/api/src/kcl.rs), lines 370-371). If present, the function is called directly without JSON marshalling overhead.
2. **JSON fallback** – If not found in the direct map, the evaluator falls back to `kcl_plugin_invoke_json`. The handler receives the fully-qualified method name, marshals arguments to JSON, and returns a JSON result that the runtime converts back to a KCL value (see [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs), lines 68-84).

## Creating a Custom KCL Plugin in Rust

Below is a complete workflow for building a native plugin that exposes a `say_hello(name: str) -> str` function to KCL configurations.

### Step 1: Implement the Plugin Library

Create a Rust library that exposes the required C-ABI entry points and registers your custom logic:

```rust
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use kcl_runtime::{PluginFunction, Value, Context};

/// The actual function implementation called from KCL.
fn say_hello(_ctx: &mut Context, args: &Value, _kwargs: &Value) -> kcl_runtime::Result<Value> {
    // Extract the first positional argument as a string.
    let name = args.list_get(0)?.str()?.to_owned();
    Ok(Value::str(&format!("Hello, {}!", name)))
}

/// C-ABI entry point: initializes the plugin and returns a function map.
#[no_mangle]
pub extern "C-unwind" fn plugin_init() -> *mut std::collections::HashMap<String, PluginFunction> {
    let mut map = std::collections::HashMap::new();
    // Register with the fully qualified short name.
    map.insert("my_pkg.say_hello".to_string(), 
               Box::new(say_hello) as PluginFunction);
    Box::into_raw(Box::new(map))
}

/// Required C-ABI entry point for the KCL runtime dispatcher.
#[no_mangle]
pub unsafe extern "C-unwind" fn kcl_plugin_init(
    fn_ptr: extern "C-unwind" fn(
        method: *const c_char,
        args_json: *const c_char,
        kwargs_json: *const c_char,
    ) -> *const c_char,
) {
    unsafe { kcl_plugin::kcl_plugin_init(fn_ptr) };
}

```

### Step 2: Compile the Shared Library

Build the project as a dynamic library compatible with your target platform:

```bash
cargo build --release --lib --crate-type cdylib

```

This produces `libmy_plugin.so` on Linux, `libmy_plugin.dylib` on macOS, or `my_plugin.dll` on Windows.

### Step 3: Load and Execute from the CLI

Use the `--plugin-agent` flag to provide a raw pointer to the plugin's initialization function. The `kcl run` command automatically sets `opts.load_plugins = true` when a non-zero agent pointer is supplied, as implemented in [`crates/runner/src/runner.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runner/src/runner.rs) (lines 165-170):

```bash

# Obtain the function pointer (example using Python ctypes for demonstration)

PLUGIN_PTR=$(python3 -c "
import ctypes
lib = ctypes.CDLL('./target/release/libmy_plugin.so')
print(ctypes.cast(lib.plugin_init, ctypes.c_void_p).value)
")

# Execute the KCL file with the plugin loaded

kcl run ./config.k --plugin-agent $PLUGIN_PTR

```

### Step 4: Consume the Plugin in KCL

Create a KCL configuration file that imports the plugin module using the `kcl_plugin.` prefix and calls the exposed function:

```kcl
import kcl_plugin.my_pkg

# Call the native function with a string argument

greeting = kcl_plugin.my_pkg.say_hello("Configuration World")

# Output the result

print(greeting)  # => "Hello, Configuration World!"

```

When the KCL evaluator processes this file, it resolves the `kcl_plugin.my_pkg` import through the runtime's `plugin_functions` map or falls back to the JSON-based invocation handler defined in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs).

## Summary

Extending KCL configurations with custom functions or external plugins involves understanding the dual architecture of the extension system:

- **Native plugins** require compiling shared libraries that expose `kcl_plugin_init` and `kcl_plugin_invoke_json` entry points, enabling high-performance integration with system resources.
- **KCL functions** provide lightweight reusability within `.k` files without requiring external compilation steps.
- The **`kcl_plugin.`** module prefix and **`load_plugins`** flag serve as the gatekeepers that allow the parser and evaluator to recognize and resolve external dependencies.
- Runtime resolution occurs through the `plugin_functions` map in [`crates/api/src/kcl.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/api/src/kcl.rs) or via JSON marshalling through the handler in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs).

## Frequently Asked Questions

### How do I enable plugin support when running KCL programmatically?

When invoking KCL through the Rust API or other language bindings, set the `load_plugins` field in `ParserOptions` to `true` and provide a valid plugin agent pointer. In [`crates/runner/src/runner.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runner/src/runner.rs) (lines 165-170), the runner automatically enables this flag when a non-zero `plugin_agent` argument is supplied via the CLI.

### What is the difference between direct plugin registration and the JSON fallback mechanism?

Direct registration involves inserting function pointers into the `plugin_functions` `HashMap` in [`crates/api/src/kcl.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/api/src/kcl.rs) (lines 370-371), allowing zero-overhead calls from KCL to native code. The JSON fallback, implemented in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs) (lines 68-84), marshals arguments to JSON strings and invokes `kcl_plugin_invoke_json` when direct registration is unavailable, providing flexibility at the cost of serialization overhead.

### Can I write KCL plugins in languages other than Rust?

Yes, any language capable of producing C-ABI compatible shared libraries can implement KCL plugins. The requirements are exposing `kcl_plugin_init` to register the dispatcher and `kcl_plugin_invoke_json` to handle method calls with JSON serialization. Languages like C, C++, Go, or Zig can be used, provided they can export the required unmangled symbols and handle the JSON-based argument passing defined in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs).

### What naming convention must I follow for plugin modules?

All plugin imports must use the `kcl_plugin.` prefix, defined as `PLUGIN_MODULE_PREFIX` in [`crates/sema/src/plugin/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/plugin/mod.rs). When registering functions in the native plugin, use the format `<package_name>.<function_name>` (e.g., `my_pkg.say_hello`), which KCL resolves as `kcl_plugin.my_pkg.say_hello` in configuration code. The parser validates this prefix in [`crates/parser/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/parser/src/lib.rs) when the `load_plugins` flag is enabled.