Heco Feature Gen

Feature scaffolding made simple

HomeDocsExamples
v1.0.0GitHub
Examples
OverviewBasic FeatureCustom ConfigCustom GeneratorsProgrammatic UsageTemplates

Templates

Create dynamic file templates using Handlebars. This example covers everything from simple variable substitution to advanced template patterns.

How templates work

Templates are Handlebars (.hbs) files that the generator renders with context variables. The rendered output is written to the file path you specify.

Template resolution order:

  1. User templates directory (if configured in your project)
  2. Built-in templates bundled with the package

Place your custom templates in a directory relative to your project root. The generator looks there first, so you can override any built-in template by creating a file with the same name.

Available variables

Every template receives these variables:

VariableExample valueDescription
{{PascalName}}UserProfilePascalCase feature name
{{camelName}}userProfilecamelCase feature name
{{snakeName}}user_profilesnake_case feature name
{{upperName}}USER_PROFILEUPPER_CASE feature name
{{name}}user-profileOriginal kebab-case name

Example 1: Component template

Template: Component.tsx.hbs

handlebars
import React from "react"

export interface {{PascalName}}Props {
  className?: string
  children?: React.ReactNode
}

export const {{PascalName}} = ({ className, children }: {{PascalName}}Props) => {
  return (
    <div className={className}>
      {{PascalName}} component
    </div>
  )
}

export default {{PascalName}}

Generated output (for user-profile):

tsx
import React from "react"

export interface UserProfileProps {
  className?: string
  children?: React.ReactNode
}

export const UserProfile = ({ className, children }: UserProfileProps) => {
  return (
    <div className={className}>
      UserProfile component
    </div>
  )
}

export default UserProfile

Example 2: Hook template with TanStack Query

Template: hooks/useHook.ts.hbs

handlebars
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { {{PascalName}}Service } from "../services/{{camelName}}Service"
import type { {{PascalName}} } from "../types/{{PascalName}}Types"

export function use{{PascalName}}List() {
  return useQuery({
    queryKey: ["{{camelName}}"],
    queryFn: {{PascalName}}Service.getAll,
  })
}

export function use{{PascalName}}(id: string) {
  return useQuery({
    queryKey: ["{{camelName}}", id],
    queryFn: () => {{PascalName}}Service.getById(id),
  })
}

export function useCreate{{PascalName}}() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: {{PascalName}}Service.create,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["{{camelName}}"] })
    },
  })
}

export function useDelete{{PascalName}}() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: {{PascalName}}Service.delete,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["{{camelName}}"] })
    },
  })
}

Example 3: Zustand store template

Template: store/store.ts.hbs

handlebars
import { create } from "zustand"
import { devtools } from "zustand/middleware"
import type { {{PascalName}} } from "../types/{{PascalName}}Types"

interface {{PascalName}}State {
  items: {{PascalName}}[]
  selectedItem: {{PascalName}} | null
  isLoading: boolean
  error: string | null

  setItems: (items: {{PascalName}}[]) => void
  selectItem: (item: {{PascalName}} | null) => void
  setLoading: (loading: boolean) => void
  setError: (error: string | null) => void
  reset: () => void
}

const initialState = {
  items: [],
  selectedItem: null,
  isLoading: false,
  error: null,
}

export const use{{PascalName}}Store = create<{{PascalName}}State>()(
  devtools(
    (set) => ({
      ...initialState,

      setItems: (items) => set({ items }),
      selectItem: (item) => set({ selectedItem: item }),
      setLoading: (isLoading) => set({ isLoading }),
      setError: (error) => set({ error }),
      reset: () => set(initialState),
    }),
    { name: "{{PascalName}}Store" }
  )
)

Example 4: Zod validation template

Template: validations/schema.ts.hbs

handlebars
import { z } from "zod"

export const {{PascalName}}Schema = z.object({
  id: z.string().uuid().optional(),
  name: z
    .string()
    .min(1, "Name is required")
    .max(100, "Name must be 100 characters or less"),
  description: z
    .string()
    .max(500, "Description must be 500 characters or less")
    .optional(),
  status: z.enum(["active", "inactive", "draft"]).default("draft"),
  createdAt: z.string().datetime().optional(),
  updatedAt: z.string().datetime().optional(),
})

export type {{PascalName}}FormData = z.infer<typeof {{PascalName}}Schema>

Example 5: Conditional content with Handlebars helpers

Handlebars supports basic conditionals and loops. While the built-in templates keep it simple, here are patterns you can use:

Conditional export:

handlebars
{{#if isDefaultExport}}
export default {{PascalName}}
{{/if}}

Loop over custom items:

handlebars
export const {{PascalName}}Fields = [
  {{#each fields}}
  { name: "{{this}}", type: "string" },
  {{/each}}
]

Custom context merging:

The context object is extensible — you can pass additional variables when calling the API programmatically:

ts
await generateFeature("user-profile", config, {
  context: {
    isDefaultExport: true,
    fields: ["name", "email", "role"],
  },
})

Template file location

Templates are looked up in this order:

  1. User templates directory — Looked up relative to the templates path in your config
  2. Built-in templates — Bundled with the package, used as fallback

To override a built-in template, create a file with the same path in your project. For example, to override the React component template, create:

txt
your-project/
├── templates/
│   └── components/
│       └── Component.tsx.hbs

Using EJS instead of Handlebars

Optionally use EJS templates. Install the dependency:

bash
npm install ejs

Then register the engine:

ts
import { registerTemplateEngine } from "@heco-gen/features"
import ejs from "ejs"

registerTemplateEngine("ejs", async (template, context) => {
  return ejs.render(template, context)
})

Now templates with .ejs extension will use the EJS engine instead of Handlebars.

Organizing templates

txt
your-project/
├── templates/
│   ├── components/
│   │   └── Component.tsx.hbs
│   ├── hooks/
│   │   └── useHook.ts.hbs
│   ├── services/
│   │   └── service.ts.hbs
│   ├── types/
│   │   └── types.ts.hbs
│   ├── validations/
│   │   └── schema.ts.hbs
│   └── store/
│       └── store.ts.hbs

This mirrors the output structure and keeps templates organized.

Next steps

  • Custom Generators — combine templates with custom file structures
  • Programmatic Usage — pass custom context variables programmatically

Heco Feature Gen

Feature scaffolding made simple

A powerful CLI tool that creates consistent, feature-based folders with all the boilerplate code you need for modern web development.

Docs

DocumentationExamples

Community

GitHubNPM

© 2026 • Developed by Htoo Aung Phyo Lwin