# How to Create Custom QuarkdownModule Definitions with Function Registrations in Quarkdown

> Learn to create custom QuarkdownModule definitions with function registrations by writing Kotlin functions and grouping them using moduleOf. Load your module via MultiFunctionLibraryLoader.

- Repository: [Giorgio Garofalo/quarkdown](https://github.com/iamgio/quarkdown)
- Tags: how-to-guide
- Published: 2026-04-29

---

**Creating custom QuarkdownModule definitions involves writing Kotlin functions annotated with Quarkdown-specific decorators, grouping them via the `moduleOf` helper into a QuarkdownModule, and loading the module through a `MultiFunctionLibraryLoader` to generate a Library that the compiler registers.**

Quarkdown, the Markdown-based content generation system from iamgio/quarkdown, exposes its standard library through modular Kotlin collections called **QuarkdownModules**. Understanding how to create custom QuarkdownModule definitions with function registrations allows developers to extend the language with domain-specific commands that integrate seamlessly with the built-in `.function {arg}` syntax.

## Understanding the QuarkdownModule Architecture

At the core of Quarkdown’s extensibility lies the **`QuarkdownModule`** class located in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/function/library/module/QuarkdownModule.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/function/library/module/QuarkdownModule.kt). This class is essentially a mutable `HashSet` of `ExportableFunction` objects that can be created from Kotlin function references using the **`moduleOf`** helper (line 30), or composed from other modules using the overloaded `+` operator.

The **`MultiFunctionLibraryLoader`** in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/function/library/loader/MultiFunctionLibraryLoader.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/function/library/loader/MultiFunctionLibraryLoader.kt) serves as the bridge between modules and the compiler. It transforms one or more `QuarkdownModule` instances into a **`Library`** object that the compiler makes available to all Quarkdown scripts during resolution.

For global availability, the **`Stdlib`** object in [`quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Stdlib.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Stdlib.kt) aggregates all built-in modules. Adding custom modules here makes their functions available to every Quarkdown document without additional configuration.

## Step-by-Step Guide to Creating a Custom Module

### Define Native Functions with Annotations

Functions must use Quarkdown’s value types (such as `NodeValue` or `OutputValue`) and may include annotations like **`@Name`** to control the callable name and **`@LikelyBody`** to indicate which parameter should receive the brace-delimited body in Quarkdown syntax.

```kotlin
// src/main/kotlin/com/example/stdlib/Custom.kt
package com.example.stdlib

import com.quarkdown.core.function.value.NodeValue
import com.quarkdown.core.function.value.wrappedAsValue
import com.quarkdown.core.function.reflect.annotation.Name

/** Simple greeting function callable as .greet {World} */
@Name("greet")
fun greet(name: String): NodeValue =
    "Hello, $name!".wrappedAsValue()

```

### Group Functions into a QuarkdownModule

Use the **`moduleOf`** factory function to collect your function references into a module. This creates the actual `QuarkdownModule` instance that tracks your `ExportableFunction` entries.

```kotlin
import com.quarkdown.core.function.library.module.QuarkdownModule
import com.quarkdown.core.function.library.module.moduleOf

/** Exporter for the custom stdlib module */
val Custom: QuarkdownModule = moduleOf(::greet)

```

### Load the Module into a Library

Create a `LibraryExporter` implementation that uses `MultiFunctionLibraryLoader` to convert your module into a compiler-recognized `Library`. This approach keeps your module separate from the global stdlib, requiring explicit imports in Quarkdown documents.

```kotlin
import com.quarkdown.core.function.library.Library
import com.quarkdown.core.function.library.LibraryExporter
import com.quarkdown.core.function.library.loader.MultiFunctionLibraryLoader

object CustomLib : LibraryExporter {
    override val library: Library
        get() = MultiFunctionLibraryLoader(name = "custom")
            .load(Custom)
}

```

### Register in the Global Stdlib (Optional)

To make your functions available globally without explicit library imports, extend the built-in **`Stdlib`** object by editing [`quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Stdlib.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Stdlib.kt):

```kotlin
// Inside Stdlib.object
import com.example.stdlib.Custom

object Stdlib : LibraryExporter {
    override val library: Library
        get() = MultiFunctionLibraryLoader(name = "stdlib")
            .load(
                Document,
                Layout,
                Text,
                Custom          // <-- Include your module here
            )
}

```

After recompiling the project, commands like `.greet {World}` become available in any Quarkdown document.

## Composing Multiple Modules

Because `QuarkdownModule` implements the `+` operator, you can combine multiple modules before loading. This is particularly useful for organizing large codebases into logical groups while exposing them through a single library.

```kotlin
val AllMyStuff = Custom + AnotherModule + Text

// Load the composed module
val library = MultiFunctionLibraryLoader(name = "combined")
    .load(AllMyStuff)

```

The resulting module behaves as a unified set of functions, with the `MultiFunctionLibraryLoader` registering all contained exportable functions into the same namespace.

## Execution Flow and Value Mapping

When Quarkdown encounters a call like `.greet {World}`, the execution proceeds through four stages:

1. **Parsing**: The parser creates a `FunctionCallNode` representing the invocation.
2. **Resolution**: The node's `Context` queries the `Library` (built by `MultiFunctionLibraryLoader`) to resolve the name "greet" to your `ExportableFunction`.
3. **Execution**: The function receives arguments as Kotlin types, computes the result, and returns a `NodeValue`.
4. **Mapping**: A `NodeOutputValueVisitor` converts the returned value into an AST node consumable by rendering stages (HTML, PDF, or plain-text).

## Summary

- **`QuarkdownModule`** acts as a container for `ExportableFunction` instances, implemented as a `HashSet` in [`QuarkdownModule.kt`](https://github.com/iamgio/quarkdown/blob/main/QuarkdownModule.kt).
- Use **`moduleOf`** to instantiate modules from Kotlin function references, or the `+` operator to compose existing modules.
- **`MultiFunctionLibraryLoader`** transforms modules into `Library` objects that the compiler registers during script compilation.
- The **`Stdlib`** object in [`Stdlib.kt`](https://github.com/iamgio/quarkdown/blob/main/Stdlib.kt) provides the central aggregation point for globally available modules.
- Functions must return Quarkdown value types (e.g., `NodeValue`) and can use annotations like `@Name` to control their external callable names.

## Frequently Asked Questions

### What return types are required for functions registered in a QuarkdownModule?

Functions must return Quarkdown-specific value types such as **`NodeValue`** or **`OutputValue`**. The core library provides utility extensions like `.wrappedAsValue()` to convert standard Kotlin types (Strings, Ints) into the required wrapper types before returning.

### How can I make a custom QuarkdownModule available without modifying the global Stdlib?

Create a standalone **`LibraryExporter`** implementation that loads your module via `MultiFunctionLibraryLoader`, then reference this library explicitly in your Quarkdown documents or CLI configuration. This keeps your custom functions isolated from the global namespace while still making them callable.

### Can I combine multiple QuarkdownModules into a single library registration?

Yes. The **`QuarkdownModule`** class overloads the `+` operator to allow composition. You can merge modules using `val Combined = ModuleA + ModuleB` and pass the result to a single `MultiFunctionLibraryLoader.load()` call, registering all functions under one library name.

### What annotations are available for controlling function behavior in Quarkdown?

The reflection system supports annotations like **`@Name`** (to specify the callable name) and **`@LikelyBody`** (to indicate which parameter receives the brace-delimited content in Quarkdown syntax). These annotations are processed when the `MultiFunctionLibraryLoader` converts Kotlin functions into `ExportableFunction` instances.