Skip to main content

Extend Codegen CLI - Templates and Debugging

Last updated on January 27, 2026

Overview

This guide provides an in-depth look at how the Extend Codegen CLI generates SDK code from OpenAPI 2.0 specifications. Understanding the template engine, custom filters, and debugging tools will help you troubleshoot issues and potentially customize the code generation process.

This information applies to all SDK template packs (Unreal, Unity, Go, etc.) as they all use the same underlying Jinja2 templating engine with AccelByte-specific extensions.

The Code Generation Workflow

At its core, the Extend Codegen CLI is a simple template-based code generator. It takes two inputs and produces one output:

How the Makefile Orchestrates Multiple Generations

When you run make all in a template pack, it's actually calling the Extend Codegen CLI multiple times with different template and output combinations:

# Simplified example of what the Unreal Makefile does:

# Generation 1: Model classes
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/operations-models.j2 \
--output Models/GuildModels.h

# Generation 2: Client API declarations
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-operations-decl.j2 \
--output Api/GuildApi.h

# Generation 3: Client API implementations
docker run ... renderc config.yaml \
--input spec/guild.json \
--template templates/Client-operations-def.j2 \
--output Api/GuildApi.cpp

# ... and so on for each file type

Each make target is essentially:

  • Same INPUT 1: Your OpenAPI spec file (e.g., guild.json)
  • Different INPUT 2: A specific template file (e.g., operations-models.j2 for models, Client-operations-decl.j2 for headers)
  • Different OUTPUT: Target file in the appropriate directory with the appropriate name
Exploring the Template Pack

To understand how code generation works for your SDK:

  1. Download and extract accelbyte-unreal-sdk-template-pack.zip
  2. Open the Makefile - each target shows which template, input, and output files are used
  3. Examine template files in templates/sdk_customization/ to see Jinja2 syntax
  4. Compare templates with generated .h and .cpp files to see the transformation

This hands-on exploration is the best way to understand the generation process.

Jinja2 Template Engine

The code generation uses Jinja2, a powerful Python templating language.

Basic Template Syntax

{# This is a comment - not included in output #}

{{ variable }} {# Output a variable value #}

{% for item in items %} {# Loop through a list #}
Process {{ item }}
{% endfor %}

{% if condition %} {# Conditional logic #}
Do something
{% elif other_condition %}
Do something else
{% else %}
Default action
{% endif %}

{% set my_var = "value" %} {# Set a variable #}

{% import 'module.j2' as module %} {# Import macros from another template #}

{% macro func_name(params) %} {# Define a reusable template function #}
Macro content
{% endmacro %}

Example from SDK Templates

Here's a real example from the Unreal SDK template (Client-operations-decl.j2):

{% import 'common/services.j2' as services -%}
{% set w_class_name = service.file_stem | to_pascal %}
{% set model_prefix = "FAccelByte" + service.file_stem | to_pascal %}

class ACCELBYTEUE4SDKCUSTOMIZATION_API {{ w_class_name }} : public Core::FApiBase
{
public:
{% for operation in operations %}
/**
* @brief {{ operation.summary }}.
*/
{% if operation.responses[0].field %}
{% set response_type = operation.responses[0].field | field_unreal_type(model_prefix) %}
THandler<{{ response_type }}> const& OnSuccess,
{% endif %}
{% endfor %}
};

This generates C++ code like:

class ACCELBYTEUE4SDKCUSTOMIZATION_API Guild : public Core::FApiBase
{
public:
/**
* @brief Create a new guild.
*/
THandler<FAccelByteGuildModelsCreateGuildResponse> const& OnSuccess,
};

Custom Jinja2 Filters

The Extend Codegen CLI provides extensive custom filters for code generation. These filters transform data from the OpenAPI spec into language-specific code.

The filters are organized into several categories:

Case Conversion Filters

Convert between different naming conventions (PascalCase, camelCase, snake_case, kebab-case, etc.). These are essential for generating code that follows language-specific naming conventions.

Example usage:

{% set class_name = service.file_stem | to_pascal %}
class {{ class_name }} {
// Converts "guild_service" to "GuildService"
}

Common filters: to_pascal, to_camel, to_snake, to_kebab, to_sentence, to_title

Language-Specific Type Filters

Convert OpenAPI types to target language types. Each SDK (Unreal, Unity, Go, etc.) has its own set of type conversion filters.

Unreal C++ filters:

  • Type conversion: field_unreal_type, unreal_class, unreal_enum_value
  • Formatting: unreal_var, unreal_enum_name, field_unreal_param
  • Validation: field_unreal_is_list_type

C# (Unity) filters:

  • Type conversion: field_csharp_type, csharp_class, csharp_enum_value
  • Formatting: csharp_prop, csharp_var, csharp_enum_name
  • Generic handling: csharp_generic_names

Go filters:

  • Type conversion: field_go_type, go_struct, go_var

Example:

{% for field in model.properties %}
{{ field | field_unreal_type("FAccelByte") }} {{ field.name | to_pascal }};
// Generates: FString GuildName;
{% endfor %}

String Manipulation Filters

Transform and manipulate strings for code generation:

  • Add/remove: prefix, suffix, affix, removeprefix, removesuffix
  • Parse: split, strip, substr
  • Format: inquotes

Regular Expression Filters

Pattern matching and text extraction:

  • Search: regex_search, regex_findall
  • Filter: alnumonly, alphaonly, digitonly, numericonly

File and Content Filters

Include external content in templates:

  • filecontents - Read entire file contents
  • filesnippet - Extract specific lines from files

Utility Filters

General-purpose transformation filters:

  • Collections: flatten, merge
  • Conditionals: onlyif, ternary, boolean
  • Type handling: typename, to_csv

Example Generation Filters

Create sample data for documentation and testing:

  • create_example - Generate example values based on OpenAPI schema
  • create_example_json - Generate JSON examples
  • create_example_wsm - Generate WebSocket message examples
tip

To see the complete list of available filters for your template pack version, use the --inspect verbose flag as described in the next section.

Debugging and Exploring with Custom Templates

One of the most powerful ways to understand code generation is to create your own simple templates and experiment with the data and filters available.

Prerequisites for Testing

To test and debug template rendering, you need three things from the template pack:

  1. config.yaml - Template pack configuration (included in every template pack)
  2. Your OpenAPI spec - The guild.json (or similar) file describing your API
  3. A template file - Either an existing .j2 file or your own custom test template

Creating a Test Template

You can create a minimal template to explore what data is available. Create a file called test-template.j2:

{# Simple template to explore available data #}

Service Information:
File Stem: {{ service.file_stem }}
Base Path: {{ service.base_path }}

Operations Count: {{ operations | length }}

First Operation (if exists):
{% if operations %}
{% set op = operations[0] %}
Summary: {{ op.summary }}
Method: {{ op.method }}
Path: {{ op.path }}
{% endif %}

{# Test filters #}
Testing Filters:
to_pascal: {{ "hello_world" | to_pascal }}
to_camel: {{ "HelloWorld" | to_camel }}
to_snake: {{ "HelloWorld" | to_snake }}

Running Your Test Template

# From within the extracted accelbyte-unreal-sdk-template-pack/ directory
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my-unreal-project/spec/guild.json \
--template test-template.j2

This outputs the rendered template to stdout, letting you see:

  • What data structures are available
  • What properties each object has
  • How filters transform values

Using --inspect verbose for Deep Exploration

The --inspect verbose flag reveals the complete template environment:

# From within accelbyte-unreal-sdk-template-pack/
docker run --rm \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
accelbyte/extend-codegen-cli:0.0.28 renderc \
config.yaml \
--input /path/to/my-unreal-project/spec/guild.json \
--template test-template.j2 \
--inspect verbose

What --inspect verbose shows:

  1. Input processor information - How your OpenAPI spec is parsed
  2. Available filters - Complete list of all Jinja2 filters (200+) with their function signatures
  3. Global functions - Built-in functions like range, dict, lipsum
  4. Test functions - Conditionals like is odd, is defined, is divisibleby
  5. Template loader paths - Where templates are searched
Practical Debugging Workflow
  1. Start simple: Create a barebones template that just prints variable names
  2. Inspect data structure: Use {{ variable }} to see what's in each object
  3. Test filters: Try different filters on sample data to see their output
  4. Compare with existing templates: Look at the template pack's templates to see real-world usage
  5. Save inspect output: Run --inspect verbose > filters.txt to reference later

Example exploration template:

{# Debug template - see all available data #}
Service: {{ service }}
Operations: {{ operations }}
Models: {{ models }}

Iterative Template Development

Recommended approach:

  1. Extract template pack and navigate to its directory
  2. Copy an existing template as a starting point (e.g., operations-models.j2)
  3. Create a simple test case - minimal OpenAPI spec with one or two endpoints
  4. Modify and test - make small changes and re-run immediately
  5. Use --inspect verbose - when you need to find a specific filter or check availability

Common Debugging Scenarios

Finding the right filter:

# Save inspect output and search it
docker run ... --inspect verbose > inspect.txt
grep "unreal" inspect.txt # Find Unreal-specific filters
grep "to_" inspect.txt # Find case conversion filters

Testing filter behavior:

{# Create a test template #}
Original: guild_service
to_pascal: {{ "guild_service" | to_pascal }}
to_camel: {{ "guild_service" | to_camel }}
to_snake: {{ "GuildService" | to_snake }}

Understanding data structure:

{# Explore what properties exist #}
{% for operation in operations %}
Operation {{ loop.index }}:
Path: {{ operation.path }}
Method: {{ operation.method }}
Summary: {{ operation.summary }}
Parameters: {{ operation.parameters | length }}
{% endfor %}

Configuration File (config.yaml)

The config.yaml file in each template pack configures the rendering process:

input_processor: 'accelbyte_codegen.ext.openapi2.legacy.OA2LegacyProcessor'
template_processor: 'textf'
renderer: 'accelbyte_codegen.ext.openapi2.legacy.OA2LegacyRenderer'
extension:
- '*jinja' # Standard Jinja2 extensions
- 'accelbyte_codegen.ext.openapi2.legacy.OA2LegacyExtension' # Custom filters
loader:
- './templates' # Template search path
output: 'stdout'

Key fields:

  • input_processor: Handles OpenAPI 2.0 parsing
  • renderer: Applies templates to parsed data
  • extension: Loads custom filters and functions
  • loader: Directories to search for templates

Common Debugging Scenarios

Generated Code Has Wrong Type

Problem: OpenAPI integer generates string instead of int.

Debug steps:

  1. Check the OpenAPI spec: Ensure the field is correctly defined as "type": "integer"
  2. Use --inspect verbose to find the correct type conversion filter for your target language
  3. Examine the template file to see which filter is being applied
  4. Verify the filter is called with the correct parameters

Missing or Incorrect Namespace

Problem: Generated code has wrong namespace or package name.

Debug steps:

  1. Verify the basePath in your OpenAPI spec is correct
  2. Check the OpenAPI info.title field
  3. Use --inspect verbose to search for namespace-related filters
  4. Examine how the template constructs namespace/package names

Custom Filter Not Working

Problem: Template uses a filter but output is incorrect or missing.

Debug steps:

  1. Run --inspect verbose to verify the filter exists in your template pack version
  2. Check the filter name spelling in your template
  3. Verify the input data type matches what the filter expects (filters may behave differently with strings vs. objects)
  4. Test with a minimal template to isolate the issue

Template Not Found Error

Problem: Code generation fails with "template not found" error.

Debug steps:

  1. Run --inspect verbose to see the template loader paths
  2. Verify the template file exists at the expected location
  3. Check for typos in the template filename or path
  4. Ensure Docker volume mounts include the template directory

Customizing Templates (Advanced)

While the template packs are designed to work out of the box, you can customize them for specific needs.

caution

Customizing templates may make it difficult to upgrade to newer template pack versions. Document your changes carefully.

Steps to Customize

  1. Extract the template pack:

    unzip accelbyte-unreal-sdk-template-pack.zip
    cd accelbyte-unreal-sdk-template-pack/
  2. Locate the template to modify:

    Templates are in templates/sdk_customization/:

    • operations-decl.j2 - API class declarations
    • operations-def.j2 - API class implementations
    • operations-models.j2 - Model definitions
  3. Make your changes:

    Edit the Jinja2 template files using the filters documented above.

  4. Test your changes:

    Run the make command and verify the generated output.

  5. Document your customizations:

    Keep notes on what you changed and why for future reference.

Example Customization

Add a custom comment to all generated API classes:

{# In operations-decl.j2 #}
class {{ w_class_name }} : public Core::FApiBase
{
// Custom comment: Generated by AccelByte Extend Codegen CLI
// OpenAPI file: {{ service.file_stem }}.json
// Generated on: {{ "now" | strftime("%Y-%m-%d") }}
public:
{% for operation in operations %}
...
{% endfor %}
};

Additional Resources

Summary

Understanding the Extend Codegen CLI's template system empowers you to:

  • Debug code generation issues effectively using --inspect verbose
  • Understand how OpenAPI specs map to generated code through Jinja2 templates
  • Use the right filters for your target language and use case
  • Customize templates when needed for special requirements

The combination of Jinja2's flexibility and AccelByte's extensive custom filters creates a powerful code generation system that can target multiple programming languages and SDKs from a single OpenAPI specification.