Skip to main content

Custom Templates

Haft supports custom code generation templates, allowing you to tailor generated code to match your project's coding standards, conventions, and requirements.

Template Priority

When generating code, Haft looks for templates in the following order:

  1. Project-level (.haft/templates/) - highest priority
  2. Global user-level (~/.haft/templates/)
  3. Built-in embedded templates - fallback

This allows you to:

  • Override templates at the project level for project-specific customizations
  • Set global defaults in your home directory for consistent personal preferences
  • Fall back to Haft's built-in templates for anything you haven't customized

Simple Template Syntax

Haft provides a user-friendly template syntax that's easy to learn:

Variables

Use ${VariableName} for simple variable substitution:

package ${BasePackage}.controller;

public class ${Name}Controller {
private final ${Name}Service ${nameCamel}Service;

@GetMapping("/api/${namePlural}")
public List<${Name}> getAll() {
return ${nameCamel}Service.findAll();
}
}

Available Variables

VariableDescriptionExample
${Name}Resource name (PascalCase)User
${name}Resource name (lowercase)user
${nameCamel}Resource name (camelCase)user
${nameSnake}Resource name (snake_case)user_name
${nameKebab}Resource name (kebab-case)user-name
${namePlural}Pluralized lowercase nameusers
${NamePlural}Pluralized PascalCase nameUsers
${BasePackage}Base package pathcom.example.app
${Package}Full package pathcom.example.app.controller
${IDType}Entity ID typeLong or UUID
${TableName}Database table nameusers

Run haft template validate --vars to see all available variables.

Conditionals

Use comment-based conditionals with // @if, // @else, and // @endif:

package ${BasePackage}.entity;

// @if HasLombok
import lombok.Data;
import lombok.Builder;
// @else
// Manual getters/setters required
// @endif

// @if HasLombok
@Data
@Builder
// @endif
public class ${Name} {

// @if UsesUUID
private UUID id;
// @else
private Long id;
// @endif

private String name;
}

Available Conditions

ConditionDescription
HasLombokTrue if Lombok is available
HasJpaTrue if Spring Data JPA is available
HasValidationTrue if Bean Validation is available
HasMapStructTrue if MapStruct is available
HasSwaggerTrue if Swagger/OpenAPI is available
HasBaseEntityTrue if project has a base entity class
UsesUUIDTrue if entity uses UUID as ID type
UsesLongTrue if entity uses Long as ID type

Run haft template validate --conditions to see all available conditions.

Managing Templates

Initialize Custom Templates

Copy built-in templates to your project for customization:

# Initialize all templates
haft template init

# Initialize only resource templates
haft template init --category resource

# Initialize only test templates
haft template init --category test

# Initialize templates globally
haft template init --global

# Overwrite existing templates
haft template init --force

List Templates

View all available templates and their sources:

# List all templates
haft template list

# List only custom templates
haft template list --custom

# List specific category
haft template list --category resource

# Show full paths
haft template list --paths

Validate Templates

Check templates for errors before using them:

# Validate all project templates
haft template validate

# Validate a specific template
haft template validate .haft/templates/resource/layered/Controller.java.tmpl

# Validate a directory
haft template validate .haft/templates/resource/

# Show available variables
haft template validate --vars

# Show available conditions
haft template validate --conditions

Validation checks for:

  • Unclosed placeholders (missing })
  • Unmatched @if/@endif directives
  • Unknown variables (warnings)
  • Template syntax errors

Template Structure

Templates are organized by category:

.haft/templates/
├── resource/
│ ├── layered/
│ │ ├── Controller.java.tmpl
│ │ ├── Service.java.tmpl
│ │ ├── ServiceImpl.java.tmpl
│ │ ├── Repository.java.tmpl
│ │ ├── Entity.java.tmpl
│ │ ├── Request.java.tmpl
│ │ ├── Response.java.tmpl
│ │ └── Mapper.java.tmpl
│ └── feature/
│ └── ... (same files)
├── test/
│ ├── layered/
│ │ ├── ServiceTest.java.tmpl
│ │ ├── ControllerTest.java.tmpl
│ │ └── ...
│ └── feature/
│ └── ...
└── project/
├── Application.java.tmpl
├── pom.xml.tmpl
└── ...

Advanced: Go Template Syntax

For complex logic, you can also use Go's text/template syntax directly:

package {{.BasePackage}}.controller;

{{if .HasValidation}}
import jakarta.validation.Valid;
{{end}}

public class {{.Name}}Controller {
{{range .Fields}}
private {{.Type}} {{.Name}};
{{end}}
}

The simple ${var} syntax and Go template syntax can be mixed in the same file.

Common Customizations

/*
* Copyright (c) 2024 MyCompany Inc.
* All rights reserved.
*/
package ${BasePackage}.controller;

Custom Annotations

// @if HasLombok
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
// @endif
@Audited
@Cacheable
public class ${Name} {

Using Records for DTOs

package ${BasePackage}.dto;

// @if HasValidation
import jakarta.validation.constraints.*;
// @endif

public record ${Name}Request(
// @if HasValidation
@NotBlank
// @endif
String name,

// @if HasValidation
@Email
// @endif
String email
) {}

Custom Repository Methods

@Repository
public interface ${Name}Repository extends JpaRepository<${Name}, ${IDType}> {

Optional<${Name}> findByEmail(String email);

List<${Name}> findByActiveTrue();

@Query("SELECT e FROM ${Name} e WHERE e.createdAt > :date")
List<${Name}> findRecentlyCreated(@Param("date") LocalDateTime date);
}

Tips

  1. Validate First: Always run haft template validate after modifying templates
  2. Start Small: Begin by customizing just one template to understand the process
  3. Use Version Control: Track your custom templates in .haft/templates/ with git
  4. Share Templates: Copy project templates to ~/.haft/templates/ for use across projects
  5. Check Variables: Use haft template validate --vars to see what's available