# How to Implement Custom Document Types in Quarkdown: Extending Beyond Plain, Paged, Slides, and Docs

> Extend Quarkdown's capabilities by implementing custom document types. Learn to add new enums and integrate rendering logic for unique document formats beyond standard options.

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

---

**You can implement custom document types in Quarkdown by adding a new enum constant to [`DocumentType.kt`](https://github.com/iamgio/quarkdown/blob/main/DocumentType.kt), exposing it via the `.doctype` function in the standard library, and wiring any rendering-specific logic such as page formats, numbering schemes, or third-party assets.**

Quarkdown ships with four built-in document types—**PLAIN**, **PAGED**, **SLIDES**, and **DOCS**—that determine default page sizing, heading numbering, and which JavaScript libraries get injected during HTML rendering. Because `DocumentType` is defined as a Kotlin enum, adding support for custom types like *magazine* or *book* requires modifying the core source code and updating the pipeline that consumes this type.

## Understanding the DocumentType Architecture

A **document type** in Quarkdown is an enum constant that lives in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt). Each type influences four critical areas of the rendering pipeline:

- **Default page format** – The `defaultPageFormat` parameter (a `PageFormatInfo` object) defines width, height, and orientation used by the HTML post-renderer and PDF generator.
- **Default numbering** – The `defaultNumbering` parameter (a `DocumentNumbering` object) sets the initial formats for headings, figures, tables, and math expressions.
- **Function constraints** – Native functions can be gated to specific types using the `@OnlyForDocumentType` and `@NotForDocumentType` annotations.
- **Third-party assets** – The HTML post-renderer checks `context.documentInfo.type` to decide which CSS or JavaScript bundles to include, such as Reveal.js for slides.

```kotlin
// DocumentType enum signature (quarkdown-core/.../DocumentType.kt)
enum class DocumentType(
    val preferredOrientation: PageOrientation,
    val defaultPageFormat: PageFormatInfo? = null,
    val defaultNumbering: DocumentNumbering? = null,
)

```

Because this is a compiled enum, you cannot add types at runtime; you must extend the source and rebuild the compiler.

## Step-by-Step Implementation Guide

### 1. Add the Enum Entry in DocumentType.kt

Create a new constant in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt). Supply the `preferredOrientation`, and optionally `defaultPageFormat` and `defaultNumbering`. Insert the constant after the existing `DOCS` entry or alongside it.

```kotlin
MAGAZINE(
    preferredOrientation = PageOrientation.PORTRAIT,
    // 5.5" × 8.5" in points (1 inch = 72 points)
    defaultPageFormat = PageFormatInfo(
        pageWidth = 5.5 * 72,
        pageHeight = 8.5 * 72
    ),
    defaultNumbering = DocumentNumbering(
        headings = NumberingFormat.fromString("1."),
        figures = NumberingFormat.fromString("(1)"),
        tables = NumberingFormat.fromString("Table (1)"),
        math = NumberingFormat.fromString("(1)")
    )
)

```

### 2. Expose the Type via the .doctype Function

The user-facing `.doctype` function is implemented in [`quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Document.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Document.kt). Add a branch that maps a string identifier (e.g., `"magazine"`) to your new enum constant.

```kotlin
@Name("doctype")
fun doctype(
    @Name("type") type: String,
    @Injected context: Context
): DocumentInfo {
    val docType = when (type.lowercase()) {
        "plain"    -> DocumentType.PLAIN
        "paged"    -> DocumentType.PAGED
        "slides"   -> DocumentType.SLIDES
        "docs"     -> DocumentType.DOCS
        "magazine" -> DocumentType.MAGAZINE  // new mapping
        else -> error("Unsupported document type: $type")
    }
    return DocumentInfo(type = docType)
}

```

### 3. Configure HTML Rendering and Third-Party Assets

If your type requires special styling or scripts, update [`quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/post/document/HtmlDocumentBuilder.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/post/document/HtmlDocumentBuilder.kt) to read `context.documentInfo.type.defaultPageFormat`. Additionally, create a subclass of `ThirdPartyLibrary` in [`quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/post/thirdparty/ThirdPartyLibrary.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-html/src/main/kotlin/com/quarkdown/rendering/html/post/thirdparty/ThirdPartyLibrary.kt) to conditionally inject assets.

```kotlin
class MagazineAssets : ThirdPartyLibrary {
    override fun isRequired(context: Context) =
        context.documentInfo.type == DocumentType.MAGAZINE

    override val scriptUrls = listOf("assets/magazine-layout.css")
}

```

### 4. Apply Function Constraints (Optional)

To restrict native functions to your new type, use the annotations defined in `quarkdown-core/src/main/kotlin/com/quarkdown/core/function/reflect/annotation/`. Follow the pattern used in [`quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Slides.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Slides.kt).

```kotlin
@OnlyForDocumentType(DocumentType.MAGAZINE)
@Name("magazineheader")
fun magazineHeader(content: String) = // implementation

```

### 5. Add Tests and Documentation

Write unit tests in [`quarkdown-test/src/test/kotlin/com/quarkdown/test/DocumentTest.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-test/src/test/kotlin/com/quarkdown/test/DocumentTest.kt) that compile a `.qd` file with `.doctype {magazine}` and assert that `DocumentInfo.type` equals `DocumentType.MAGAZINE`, that the default page format is applied, and that the correct third-party bundles are requested.

## Complete Example: Adding a Magazine Document Type

Below is the full implementation of a **MAGAZINE** type that renders portrait documents at 5.5×8.5 inches with custom numbering.

**Step A:** Define the enum with custom sizing.

```kotlin
// quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt
enum class DocumentType(
    val preferredOrientation: PageOrientation,
    val defaultPageFormat: PageFormatInfo? = null,
    val defaultNumbering: DocumentNumbering? = null,
) {
    // ... existing types ...
    
    MAGAZINE(
        preferredOrientation = PageOrientation.PORTRAIT,
        defaultPageFormat = PageFormatInfo(
            pageWidth = 5.5 * 72,
            pageHeight = 8.5 * 72
        ),
        defaultNumbering = DocumentNumbering(
            headings = NumberingFormat.fromString("1."),
            figures = NumberingFormat.fromString("(1)"),
            tables = NumberingFormat.fromString("Table (1)"),
            math = NumberingFormat.fromString("(1)")
        )
    );
}

```

**Step B:** Map the user-facing string.

```kotlin
// quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Document.kt
"magazine" -> DocumentType.MAGAZINE

```

**Step C:** Register conditional assets.

```kotlin
// quarkdown-html/.../thirdparty/MagazineAssets.kt
class MagazineAssets : ThirdPartyLibrary {
    override fun isRequired(context: Context) =
        context.documentInfo.type == DocumentType.MAGAZINE
    override val styleUrls = listOf("magazine.css")
}

```

**Step D:** Use it in a Quarkdown file.

```markdown
.doctype {magazine}
.title {The Quarkdown Quarterly}

.author {Jane Doe}
.date {2024}

## Feature Story

This content renders with a 5.5×8.5 inch page size and custom numbering.

```

Running `quarkdown myfile.qd` now produces output using the magazine specifications.

## Summary

- **Add the enum constant** in [`DocumentType.kt`](https://github.com/iamgio/quarkdown/blob/main/DocumentType.kt) with orientation, page format, and numbering defaults.
- **Expose the type** by adding a string mapping in the `.doctype` function inside [`Document.kt`](https://github.com/iamgio/quarkdown/blob/main/Document.kt).
- **Wire rendering logic** in [`HtmlDocumentBuilder.kt`](https://github.com/iamgio/quarkdown/blob/main/HtmlDocumentBuilder.kt) and `ThirdPartyLibrary` implementations to handle page sizes and assets.
- **Gate functions** using `@OnlyForDocumentType` or `@NotForDocumentType` annotations when behavior should be type-specific.
- **Validate** your changes with unit tests in [`DocumentTest.kt`](https://github.com/iamgio/quarkdown/blob/main/DocumentTest.kt) that assert correct type assignment and pipeline integration.

## Frequently Asked Questions

### Can I add a custom document type without modifying the Quarkdown source code?

No. Because `DocumentType` is implemented as a Kotlin enum in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/document/DocumentType.kt), new types must be added at compile time. You cannot register custom types via plugins or configuration files; you must fork or extend the core repository and rebuild the compiler.

### How do I restrict a native function to only work with my custom document type?

Annotate the function with `@OnlyForDocumentType(DocumentType.YOUR_TYPE)` as defined in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/function/reflect/annotation/OnlyForDocumentType.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/function/reflect/annotation/OnlyForDocumentType.kt). Conversely, use `@NotForDocumentType` to exclude your type from functions that should not support it. See [`quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Slides.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-stdlib/src/main/kotlin/com/quarkdown/stdlib/Slides.kt) for a working example of slides-only functions.

### What is the difference between a document type and a page format?

A **document type** is a high-level classification (e.g., SLIDES, MAGAZINE) that carries semantic meaning, default numbering rules, and asset requirements. A **page format** is a property *of* the document type that defines physical dimensions (width/height in points or millimeters) and orientation. The document type provides the default page format via its `defaultPageFormat` parameter, but the format itself is just the geometric specification used by the HTML and PDF renderers.

### How do I add custom CSS or JavaScript for my new document type?

Create a class that implements `ThirdPartyLibrary` in the `quarkdown-html` module, overriding `isRequired(context)` to return `true` only when `context.documentInfo.type == DocumentType.YOUR_TYPE`. Return your asset URLs in `scriptUrls` or `styleUrls`. The `HtmlDocumentBuilder` will automatically include these resources when building the final HTML document for that type.