> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Kotlin Code Generation

> How the Repost Gradle plugin generates your Kotlin client: the generator block, the repostGenerateCheck CI gate, engine pinning, and multi-module aggregation.

export const RepostHighlight = () => {
  useEffect(() => {
    const colors = {
      comment: ['#116329', '#6A9955'],
      constant: ['#0550AE', '#9CDCFE'],
      keyword: ['#CF222E', '#569CD6'],
      name: ['#953800', '#4EC9B0'],
      string: ['#0A3069', '#CE9178'],
      type: ['#0550AE', '#4EC9B0'],
      variable: ['#1F2328', '#9CDCFE']
    };
    const tokenPattern = /"[^"\n]*"|@[A-Za-z_]\w*|\b(?:String|Int|Float|Boolean|DateTime|Json)\b|\b(?:true|false)\b|\b\d+(?:\.\d+)?\b|\b(?:now|uuid|cuid)(?=\s*\()|\b[A-Z]\w*\b|\b[A-Za-z_]\w*\b/g;
    const append = (line, text, color) => {
      if (!text) return;
      if (!color) {
        line.append(text);
        return;
      }
      const span = document.createElement('span');
      span.textContent = text;
      span.style.color = color[0];
      span.style.setProperty('--shiki-dark', color[1]);
      line.append(span);
    };
    const commentStart = text => {
      let quoted = false;
      for (let index = 0; index < text.length - 1; index += 1) {
        if (text[index] === '"' && text[index - 1] !== '\\') quoted = !quoted;
        if (!quoted && text[index] === '/' && text[index + 1] === '/') return index;
      }
      return -1;
    };
    const highlightCode = code => {
      if (code.dataset.repostHighlighted !== undefined || code.querySelector('span[style*="--shiki-dark"]')) {
        return;
      }
      code.dataset.repostHighlighted = '';
      const source = code.textContent ?? '';
      const fragment = document.createDocumentFragment();
      for (const text of source.split('\n')) {
        const line = document.createElement('span');
        line.className = 'line';
        const declaration = text.match(/^(\s*)(generator|model|enum|type|event)(\s+)([A-Za-z_]\w*)(.*)$/);
        if (declaration) {
          append(line, declaration[1]);
          append(line, declaration[2], colors.keyword);
          append(line, declaration[3]);
          append(line, declaration[4], colors.name);
          append(line, declaration[5]);
        } else {
          const start = commentStart(text);
          const codeText = start === -1 ? text : text.slice(0, start);
          const leading = codeText.match(/^(\s*)([A-Za-z_]\w*)(?=\s+(?:@|[A-Z]))/);
          const bare = codeText.match(/^(\s*)([A-Za-z_]\w*)(\s*)$/);
          let offset = 0;
          for (const match of codeText.matchAll(tokenPattern)) {
            append(line, codeText.slice(offset, match.index));
            const token = match[0];
            let color = colors.constant;
            if (token.startsWith('"')) color = colors.string; else if (token.startsWith('@') || (/^(now|uuid|cuid)$/).test(token)) color = colors.name; else if ((/^(String|Int|Float|Boolean|DateTime|Json)$/).test(token)) color = colors.type; else if (leading && match.index === leading[1].length) color = colors.variable; else if (bare && match.index === bare[1].length) color = colors.constant; else if ((/^[A-Z]/).test(token)) color = colors.name; else if ((/^[A-Za-z_]/).test(token)) color = colors.variable;
            append(line, token, color);
            offset = match.index + token.length;
          }
          append(line, codeText.slice(offset));
          if (start !== -1) append(line, text.slice(start), colors.comment);
        }
        fragment.append(line, '\n');
      }
      code.replaceChildren(fragment);
    };
    const highlightRepostBlocks = () => {
      for (const block of document.querySelectorAll('.code-block')) {
        const code = block.querySelector('pre code');
        const filename = block.querySelector('[data-component-part="code-block-header-filename"] [title$=".repost"]');
        const looksLikeRepost = (/^\s*(generator|model|enum|type|event)\s+[A-Za-z_]\w*\s*\{/m).test(code?.textContent ?? '');
        if (code && (filename || looksLikeRepost)) highlightCode(code);
      }
    };
    let frame;
    const schedule = () => {
      if (frame !== undefined) return;
      frame = requestAnimationFrame(() => {
        frame = undefined;
        highlightRepostBlocks();
      });
    };
    const observer = new MutationObserver(schedule);
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true
    });
    schedule();
    return () => {
      observer.disconnect();
      if (frame !== undefined) cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<RepostHighlight />

The Gradle plugin runs the schema engine as part of your build and owns two output trees: generated sources and a discovery resource. This page is the reference for that machinery; for the end-to-end setup, start at the [quickstart](/docs/send/kotlin/quickstart).

## The generator block

A Kotlin `generator` block sets four things the other languages don't need:

```repost repost/schema.repost theme={"languages":{"custom":["/languages/repost.json"]}}
generator kotlinSdk {
  language       = "kotlin"
  output         = "../build/generated/sources/repost/kotlinSdk/kotlin"
  resourceOutput = "../build/generated/resources/repost/kotlinSdk"
  packageName    = "com.example.repost"
  clientName     = "RepostClient"
}
```

| Field            | What it owns                                                                                                                                                                                          |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output`         | The **source** root. Generated `.kt` files land under the `packageName` subdirectory. Repost owns this tree — it replaces its own files atomically and never touches anything else.                   |
| `resourceOutput` | The **resource** root. One `client.json` registry per client is written to `META-INF/repost/generated-clients/v1/<id>/` here, so a framework or the aggregator can discover the client at build time. |
| `packageName`    | The Kotlin package for the generated models, `Webhooks` tree, and client.                                                                                                                             |
| `clientName`     | The generated client class name. Pick a distinct name per schema when a build hosts more than one.                                                                                                    |

Source and resources are **one transaction**: a regeneration replaces both together or restores the previous tree — a crash never leaves a half-written client or a stale registry. Point `output`/`resourceOutput` at `build/` directories (as above); they are regenerated, not committed.

## The engine

The plugin resolves and runs a signed, platform-specific schema engine (`sh.repost:repost-schema-engine:0.9.0`) — nothing else to install. Pin a different engine build with `repostSdk { engineVersion.set("…") }`; its checksum and signature are verified before it runs. The generated code emits Kotlin language/API 2.1, so it compiles on the 2.1 baseline and every newer compiler.

## Generate and check

The plugin binds `repostGenerate` before `compileKotlin`, so a normal build already has your client, and wires `repostGenerateCheck` into `check`:

<CodeGroup>
  ```bash Generate (Gradle) theme={"languages":{"custom":["/languages/repost.json"]}}
  ./gradlew repostGenerate
  ```

  ```bash Verify without mutating (Gradle) theme={"languages":{"custom":["/languages/repost.json"]}}
  ./gradlew repostGenerateCheck
  ```

  ```bash Generate (CLI) theme={"languages":{"custom":["/languages/repost.json"]}}
  repost schema generate
  ```
</CodeGroup>

`repostGenerateCheck` regenerates into an isolated directory, hashes the result, and fails if it differs from the committed output **without writing to your source tree** — the CI gate that proves generated code is in sync. The `repost` CLI produces the identical tree from the same schema.

## `GENERATE` versus multi-module `AGGREGATE_ONLY`

Every module runs in one of two modes (`repostSdk { schemaMode.set(...) }`; default `GENERATE`):

* **`GENERATE`** — run the engine on this module's schema, emit the client and its registry, and aggregate any client registries found on the compile classpath. A single-module app or a schema library uses this.
* **`AGGREGATE_ONLY`** — run **no** engine and generate **no** client. Only merge the registries published by dependency modules into one application registry. An application module that consumes clients from several schema libraries uses this so it never regenerates code it doesn't own.

```kotlin Application module — build.gradle.kts theme={"languages":{"custom":["/languages/repost.json"]}}
import sh.repost.gradle.RepostSchemaMode
import sh.repost.gradle.RepostIntegration

repostSdk {
    schemaMode.set(RepostSchemaMode.AGGREGATE_ONLY)
    integration.set(RepostIntegration.SPRING_BOOT)
}
```

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Schema workflow" icon="braces" href="/docs/cli/schema" cta="Schema" arrow="true">
    Version the schema with migrations and deploy it to your environment.
  </Card>

  <Card title="Configuration" icon="sliders-horizontal" href="/docs/send/kotlin/configuration" cta="Configure" arrow="true">
    Credentials, timeouts, retries, and the trailing-lambda config DSL.
  </Card>

  <Card title="Frameworks" icon="boxes" href="/docs/send/kotlin/frameworks" cta="Inject" arrow="true">
    Spring Boot, Jakarta CDI, Ktor, and the framework-neutral core pattern.
  </Card>
</Columns>
