# How to Implement Custom Template Processors in Quarkdown's Project Creator

> Learn to implement custom template processors in Quarkdown's project creator. Extend the create wizard by implementing ProjectCreatorTemplateProcessorFactory and registering your factory.

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

---

**Implement custom template processors by creating a class that implements `ProjectCreatorTemplateProcessorFactory`, injecting your custom placeholders into JTE templates, and registering the factory in `CreateProjectCommand` to extend the `quarkdown create` wizard.**

The `quarkdown create` command in the iamgio/quarkdown repository bootstraps new projects by rendering JTE templates through the **Project Creator** wizard. When you need to scaffold projects with custom layouts, additional configuration files, or organization-specific defaults, implementing **custom template processors** lets you inject arbitrary metadata into `.qd.jte` templates without modifying the core engine.

## Understanding the Template Processing Architecture

The Project Creator delegates all template rendering to a thin abstraction layer over `com.quarkdown.core.template.TemplateProcessor`. To build a custom processor, you extend specific factory interfaces and wire them into the CLI entry point.

### Core Components You Will Extend

- **`ProjectCreatorTemplateProcessorFactory`** – The interface that defines `create()`, returning a configured `TemplateProcessor` instance. Located in [`quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/template/ProjectCreatorTemplateProcessorFactory.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/template/ProjectCreatorTemplateProcessorFactory.kt).
- **`DefaultProjectCreatorTemplateProcessorFactory`** – The reference implementation that populates standard placeholders like `name`, `description`, and `authors`. Found in [`quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/template/DefaultProjectCreatorTemplateProcessorFactory.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/template/DefaultProjectCreatorTemplateProcessorFactory.kt).
- **`ProjectCreatorTemplatePlaceholders`** – An object containing string constants for placeholder names (e.g., `NAME`, `DESCRIPTION`). Located in [`quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/template/ProjectCreatorTemplatePlaceholders.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/template/ProjectCreatorTemplatePlaceholders.kt).
- **`TemplateProcessor`** – The low-level builder class that feeds values into JTE via `optionalValue()`, `conditional()`, and `iterable()`. Defined in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/template/TemplateProcessor.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/template/TemplateProcessor.kt).
- **`CreateProjectCommand`** – The CLI handler that selects which factory to instantiate based on user flags. Found in [`quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/command/CreateProjectCommand.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-cli/src/main/kotlin/com/quarkdown/cli/creator/command/CreateProjectCommand.kt).

## Creating a Custom JTE Template

Place your template in the CLI module's resources to ensure it is packaged with the executable.

1. Create the file at `quarkdown-cli/src/main/resources/creator/custom.qd.jte`.
2. Use JTE syntax compatible with `TemplateProcessor`:
   - **Values**: `${PLACEHOLDER}`
   - **Conditionals**: `@if(PLACEHOLDER) … @endif`
   - **Iterables**: `@for(item in PLACEHOLDER)${item}@endfor`

```qd
.doctype {plain}
.docname {${name}}
@if(description)
.docdesc {${description}}
@endif
@for(author in AUTHORS)
.author {${author}}
@endfor
@if(license)
.license {${license}}
@endif

```

3. Add any static assets (images, CSS) adjacent to the template; the resource supplier copies them to the generated project during scaffolding.

## Implementing a Custom Factory

Create a Kotlin class that implements `ProjectCreatorTemplateProcessorFactory`. You can **extend** the default factory to inherit standard placeholder wiring, or implement the interface directly for full control.

### Minimal Example: Swapping the Template Path

This implementation delegates placeholder injection to `DefaultProjectCreatorTemplateProcessorFactory` while only changing the template resource:

```kotlin
package com.quarkdown.cli.creator.template

import com.quarkdown.core.document.DocumentInfo
import com.quarkdown.core.template.TemplateProcessor

private const val CUSTOM_TEMPLATE = "/creator/custom.qd.jte"

class CustomProjectCreatorTemplateProcessorFactory(
    private val info: DocumentInfo,
) : ProjectCreatorTemplateProcessorFactory {

    override fun create(): TemplateProcessor =
        DefaultProjectCreatorTemplateProcessorFactory(info, CUSTOM_TEMPLATE).create()
}

```

### Adding New Placeholders

To inject custom data (e.g., a `license` field), extend the placeholder constants and manually configure the `TemplateProcessor`:

1. Add the constant to [`ProjectCreatorTemplatePlaceholders.kt`](https://github.com/iamgio/quarkdown/blob/main/ProjectCreatorTemplatePlaceholders.kt):

```kotlin
const val LICENSE = "license"

```

2. Implement the factory with explicit value injection:

```kotlin
class CustomProjectCreatorTemplateProcessorFactory(
    private val info: DocumentInfo,
    private val license: String? = null,
) : ProjectCreatorTemplateProcessorFactory {

    override fun create(): TemplateProcessor =
        with(ProjectCreatorTemplatePlaceholders) {
            TemplateProcessor.fromResourceName("/creator/custom.qd.jte").apply {
                optionalValue(NAME, info.name)
                optionalValue(DESCRIPTION, info.description)
                conditional(KEYWORDS, info.keywords.isNotEmpty())
                iterable(KEYWORDS, info.keywords)
                conditional(AUTHORS, info.authors.isNotEmpty())
                iterable(AUTHORS, info.authors.map { it.name })
                optionalValue(TYPE, info.type.quarkdownName)
                conditional(IS_DOCS, info.type == DocumentType.DOCS)
                
                // Custom placeholder injection
                optionalValue(LICENSE, license)
            }
        }
}

```

## Wiring the Factory into the CLI

Modify [`CreateProjectCommand.kt`](https://github.com/iamgio/quarkdown/blob/main/CreateProjectCommand.kt) to instantiate your factory when specific flags are present. The command currently branches between default and docs factories:

```kotlin
val processorFactory = if (isDocs) {
    DocsProjectCreatorTemplateProcessorFactory(documentInfo)
} else {
    DefaultProjectCreatorTemplateProcessorFactory(documentInfo)
}

```

Add a new branch for your custom implementation:

```kotlin
val isCustom = parsedOptions.hasFlag("custom")
val customLicense = parsedOptions.getOptionValue("license")

val processorFactory = when {
    isDocs   -> DocsProjectCreatorTemplateProcessorFactory(documentInfo)
    isCustom -> CustomProjectCreatorTemplateProcessorFactory(documentInfo, customLicense)
    else     -> DefaultProjectCreatorTemplateProcessorFactory(documentInfo)
}

```

After registering the flag in your CLI options definition, running `quarkdown create --custom --license MIT` generates a project using your custom template with the `${license}` placeholder resolved to "MIT".

## Complete Working Example

### File Structure

```

quarkdown-cli/
└─ src/main/
   ├─ resources/creator/
   │   └─ custom.qd.jte
   └─ kotlin/com/quarkdown/cli/creator/template/
       ├─ CustomProjectCreatorTemplateProcessorFactory.kt
       └─ ProjectCreatorTemplatePlaceholders.kt

```

### Custom Factory Implementation

```kotlin
package com.quarkdown.cli.creator.template

import com.quarkdown.core.document.DocumentInfo
import com.quarkdown.core.document.DocumentType
import com.quarkdown.core.template.TemplateProcessor

class CustomProjectCreatorTemplateProcessorFactory(
    private val info: DocumentInfo,
    private val license: String? = null,
) : ProjectCreatorTemplateProcessorFactory {

    override fun create(): TemplateProcessor =
        with(ProjectCreatorTemplatePlaceholders) {
            TemplateProcessor.fromResourceName("/creator/custom.qd.jte").apply {
                optionalValue(NAME, info.name)
                optionalValue(DESCRIPTION, info.description)
                conditional(KEYWORDS, info.keywords.isNotEmpty())
                iterable(KEYWORDS, info.keywords)
                conditional(AUTHORS, info.authors.isNotEmpty())
                iterable(AUTHORS, info.authors.map { it.name })
                optionalValue(TYPE, info.type.quarkdownName)
                conditional(IS_DOCS, info.type == DocumentType.DOCS)
                optionalValue(LANGUAGE, info.locale?.displayName)
                conditional(HAS_THEME, info.theme?.hasComponent == true)
                optionalValue(COLOR_THEME, info.theme?.color)
                optionalValue(LAYOUT_THEME, info.theme?.layout)
                conditional(USE_PAGE_COUNTER, info.type == DocumentType.PAGED)
                
                // Custom injection
                optionalValue(LICENSE, license)
            }
        }
}

```

### CLI Integration

```kotlin
// In CreateProjectCommand.kt
val isCustom = parsedOptions.hasFlag("custom")
val licenseValue = parsedOptions.getOptionValue("license")

val processorFactory = when {
    isDocs   -> DocsProjectCreatorTemplateProcessorFactory(documentInfo)
    isCustom -> CustomProjectCreatorTemplateProcessorFactory(documentInfo, licenseValue)
    else     -> DefaultProjectCreatorTemplateProcessorFactory(documentInfo)
}

```

## Summary

- **Custom template processors** in Quarkdown wrap `TemplateProcessor` to inject metadata into JTE templates during project creation.
- Implement `ProjectCreatorTemplateProcessorFactory` and override `create()` to configure placeholders via `optionalValue()`, `conditional()`, and `iterable()`.
- Delegate to `DefaultProjectCreatorTemplateProcessorFactory` to reuse standard placeholder logic while swapping template paths.
- Register your factory in [`CreateProjectCommand.kt`](https://github.com/iamgio/quarkdown/blob/main/CreateProjectCommand.kt) by adding CLI flags and conditional instantiation logic.
- Store templates in `quarkdown-cli/src/main/resources/creator/` and reference them via `TemplateProcessor.fromResourceName()`.

## Frequently Asked Questions

### Can I generate multiple files with a single custom processor?

Yes. Override `createFilenameMappings()` instead of `create()` to return a `Map<String, TemplateProcessor>` where each key is a target filename and each value is a configured processor. This lets you scaffold auxiliary files like READMEs or configuration files alongside the main `.qd` document according to the iamgio/quarkdown source architecture.

### How do I access command-line options inside my custom factory?

Pass parsed option values as constructor parameters when instantiating the factory in [`CreateProjectCommand.kt`](https://github.com/iamgio/quarkdown/blob/main/CreateProjectCommand.kt). The factory receives a `DocumentInfo` object by default; for custom flags (like `--license`), extract the values in the command handler and inject them into your factory's constructor before calling `create()`.

### What JTE syntax features does `TemplateProcessor` support?

`TemplateProcessor` supports standard JTE value interpolation with `${PLACEHOLDER}`, conditional blocks with `@if(PLACEHOLDER)…@endif`, and iteration with `@for(item in PLACEHOLDER)${item}@endfor`. These map directly to the `optionalValue()`, `conditional()`, and `iterable()` methods in [`TemplateProcessor.kt`](https://github.com/iamgio/quarkdown/blob/main/TemplateProcessor.kt).

### Is it possible to extend the default factory without copying all placeholder logic?

Yes. Compose your custom factory by accepting a `DefaultProjectCreatorTemplateProcessorFactory` as a delegate or by calling its `create()` method with a custom template path parameter. This preserves all standard injections (name, description, authors, themes) while allowing you to override only the template location or append additional placeholders afterward.