Open-source project
dave/jennifer avatar
dave/jennifer

jennifer: A Fluent Code Generator for Go

Jennifer is a code generator for Go

3,633 stars162 forksGoMIT

At a glance

What is it?
Jennifer builds Go source through a chainable API, where each call appends a piece of syntax to a statement tree and a file renders it with imports resolved and package aliases fixed. It is the library behind genjen, which generates much of Jennifer itself, and it is MIT licensed with no published releases.
Who is it for?
Jennifer fits a Go team that needs to emit source files from a schema, a template or a service definition and wants the result to be gofmt-clean with correct imports, rather than string concatenation with a formatting pass afterwards. It is a poor fit if you want a template engine, since there is no templating syntax here, and a poor fit for a small one-off script where the build step of a generator costs more than the generator saves.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 16 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Building a Syntax Tree Instead of Concatenating Strings

Jennifer is a code generator for Go, and its design choice is that you never write a template or concatenate strings. Instead, you call a chain of methods, each of which appends one piece of syntax, and the result is a tree of statements that knows how to render itself as valid Go.

The smallest complete example creates a file, adds a function to it and prints the result:

go
package main

import (
    "fmt"

    . "github.com/dave/jennifer/jen"
)

func main() {
	f := NewFile("main")
	f.Func().Id("main").Params().Block(
		Qual("fmt", "Println").Call(Lit("Hello, world")),
	)
	fmt.Printf("%#v", f)
}

Every element of Go syntax has a constructor. Id renders an identifier, Dot renders a period followed by an identifier for fields and selectors, Qual renders a qualified identifier, List renders a comma separated list, Op renders an operator or token, and Block renders a statement list in curly braces. The chains read as the code they produce, which is what makes a generator readable when the output is not trivial.

Rendering in Tests, and Why Production Uses Render or Save

The examples in the documentation render statements with the fmt package using the %#v verb, and the README is explicit that this is for testing rather than production, because any error will cause a panic. For production the recommended entry points are File.Render and File.Save.

That distinction is worth internalising, because it is the difference between a panic and an error you can handle. A single statement renders like this:

go
c := Id("a").Call(Lit("b"))
fmt.Printf("%#v", c)
// Output:
// a("b")

The %#v verb is what makes the library testable. Because a statement implements a formatting contract, a test can assert on the exact rendered text without writing a file, and the examples throughout the documentation use exactly that form to show what each constructor produces. For a code generator, where the output is the product, having that assertion style available is the difference between a test suite and a pile of golden files.

So the shape of the library is a tree of nodes with a rendering method, and the fmt verb is the fast path for inspecting a subtree, while Render and Save are the supported way to get bytes on disk.

Import Handling, Local Package Omission and Alias Collisions

Import management is the part of a Go generator that usually turns into a mess, and Jennifer takes it on. The README states that imports are automatically added when a qualified identifier is used with a File, that if the path matches the local path the package name is omitted, and that if package names conflict they are automatically renamed.

A single example shows all three behaviours at once:

go
f := NewFilePath("a.b/c")
f.Func().Id("init").Params().Block(
	Qual("a.b/c", "Foo").Call().Comment("Local package - name is omitted."),
	Qual("d.e/f", "Bar").Call().Comment("Import is automatically added."),
	Qual("g.h/f", "Baz").Call().Comment("Colliding package name is renamed."),
)

The result imports d.e/f as f and g.h/f as f1, and calls the local package without a qualifier. The renaming to f1 is what stops the generated file from failing to compile when two dependencies happen to share a package name, which is exactly the class of bug that only shows up in a consumer's build.

The README also explains the limits of the guess. It is not possible to reliably determine the package name given an arbitrary package path, so a sensible name is guessed from the path and added as an alias, while the names of all standard library packages are known and do not need aliasing. If you need more control there are File.ImportName and File.ImportAlias.

The Construct Set: Keywords, Braces, Collections and Generics

The documentation is organised as a reference, grouped by the kind of construct, which is a reasonable way to learn the surface. Keywords, predeclared identifiers and built-in functions are self explanatory and get their own constructors: Break, Chan, Const, Continue, Default, Defer, Else, Fallthrough, Func, Go, Goto, Range, Select, Type and Var for the keywords, the standard built-ins from Append through Recover, the numeric and string types, the constants True, False, Iota and Nil, and a helper named Err.

Braces are handled by four methods with a table of their prefixes and separators. Block renders a statement list in curly braces, Interface renders an interface declaration, Struct renders a struct declaration, and Values renders a composite literal with comma separated elements, giving []int{1, 2} or A{B: "c"}.

Some constructs need special cases, and the README says so: If and For, Interface and Struct, Switch and Case, Return and Map each have their own section because their syntax does not fit the general pattern. The reference also covers parentheses, control flow, collections, literals, comments, generics, helpers and the File type, so a generator that needs to emit an unusual construct will find it in the documentation rather than having to guess.

One small piece of ergonomics worth knowing: built-in functions take a list of parameters and render them appropriately, so append becomes a = append(a, b...) without special handling by the caller.

The Generator That Generates the Generator

Jennifer is used on itself. The README points at genjen, which generates much of Jennifer using data held in a data.go file, and the repository contains the genjen directory alongside the jen package, the gennames directory and the top level jennifer.go file.

That self-hosting is the strongest evidence about the quality of the output, and it also has a practical effect on how the project evolves. A change to the generator changes the generator, so the library has to stay backward compatible with the code it produces, which is a natural guard against formatting regressions.

The README also lists other real uses, zerogen and go-contentful-generator. A full suite of examples is published through the godoc examples index, and the author invites questions, code review and chat through issues, email or a pull request mention.

The repository is small and readable, with the jen/ directory holding the library, genjen/ the self-hosting generator, gennames/ the name data, and a README.md.tpl that is the template used to build the README itself, which is the same generator pattern applied to documentation.

Installation and the Go Version Floor

Installing the library is a single go get of the jen package:

bash
go get -u github.com/dave/jennifer/jen

The module is github.com/dave/jennifer and its go.mod declares go 1.20, so the library builds against Go 1.20 and later, and there are no third party dependencies in that manifest, which is worth noting for a library that is itself run in CI jobs.

The dot import in the examples is the recommended style, since the constructor names are all short, Id, Dot, Qual, Block, and a qualified import would make every call unreadable. The price is that the package name jen enters the file scope, which is why the naming of your own generator matters if it also has identifiers called File or List.

The library is published on pkg.go.dev under github.com/dave/jennifer/jen and is marked as stable in the project metadata. There is a MAINTAINERS file at the root of the repository, which is a small signal that the project has an explicit ownership model rather than a single anonymous maintainer.

No Releases, But Regular Commits

The repository publishes no GitHub releases, so there is no tag history to consult and no version to pin in a manifest. The project is not archived, and the last push was on 2026-10-07.

For a library consumed as a Go module this is less severe than it would be for a binary, because Go modules are versioned by commit and pseudo-version rather than by release tag, and the module path resolves to the latest commit on the default branch unless you pin one. The practical consequence is that the version you get is the tip of master at the moment you run go get, so pinning a commit in a go.sum is the way to get reproducibility, and the -u flag in the documented install command deliberately updates to the latest.

The stability signal in the project comes from elsewhere. The package carries a stable stability badge and a pkg.go.dev documentation badge, the go.mod pins a modern Go version, and the self-hosting generator keeps the output format consistent. What the project does not offer is a changelog of releases, a support policy per version, or a migration guide, so a team that wants a formal support commitment will not find one here.

Editorial conclusion

Jennifer fits a Go team that needs to emit source files from a schema, a template or a service definition and wants the result to be gofmt-clean with correct imports, rather than string concatenation with a formatting pass afterwards. It is a poor fit if you want a template engine, since there is no templating syntax here, and a poor fit for a small one-off script where the build step of a generator costs more than the generator saves. Before adopting it, read the File section, because Render and Save behave differently from the %#v rendering used in the examples, and note that the package is a dot import, so aliasing the jen package avoids polluting the namespace of your generator. The module targets Go 1.20, the licence is MIT, and the last push was on 2026-10-07.

Frequently asked questions

How do I install Jennifer?

With `go get -u github.com/dave/jennifer/jen`. The module is github.com/dave/jennifer and its go.mod declares go 1.20, with no third party dependencies listed.

How do I test code generated with Jennifer?

The README suggests rendering a File or Statement with the fmt package using the %#v verb for testing, which is how the documentation examples assert their output. It is not recommended for production use, because any error will cause a panic, so File.Render or File.Save are the preferred entry points there.

How does Jennifer handle import name conflicts?

Imports are added automatically when a qualified identifier is used with a File, a package name matching the local path is omitted, and conflicting package names are automatically renamed, for example importing g.h/f as f1 when f is already taken. For finer control there is File.ImportName and File.ImportAlias.

Is Jennifer used to generate real Go projects?

Yes. The README lists genjen, which generates much of Jennifer itself using data in data.go, along with zerogen and go-contentful-generator, and it points to an examples suite in the godoc examples index.

Which Go version does Jennifer require?

The go.mod file in the repository declares go 1.20, so the library targets Go 1.20 and later. The repository is not archived and the last push was on 2026-10-07, and the project publishes no GitHub releases.

Official sources

  1. dave/jennifer on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dave-jennifer.svg)](https://hysenlabs.com/projects/dave-jennifer)

Was this useful?