Open-source project
sbt/sbt-buildinfo avatar
sbt/sbt-buildinfo

sbt-buildinfo: generating a BuildInfo object from the build definition itself

I know this because build.sbt knows this.

557 stars90 forksScalaMIT

At a glance

What is it?
sbt-buildinfo is an sbt plugin that generates a Scala object holding the values your build already knows, such as the project name, version, Scala version and sbt version, so runtime code can report what it was built from. It suits a library that wants its version baked into the artefact, and it is published under the com.eed3si9n organisation.
Who is it for?
sbt-buildinfo is worth adding if you ship a library that needs to know its own version at runtime, because the values come from the build definition rather than from a constant somebody has to remember to update, and because the generated object is ordinary Scala that compiles into the artefact with no reflection or runtime dependency. It is the wrong tool if a resource file would do, and it is the wrong tool for a value that changes outside the build.
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 29 days ago.
What is it written in?
Mainly Scala, according to GitHub's language statistics.

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

Editorial analysis

What the plugin generates, and the problem it solves

sbt-buildinfo generates Scala source from your build definitions, and the project describes itself with a line that is more of a motto than a description: I know this because build.sbt knows this.

The problem is a small, permanent annoyance. A library usually wants to expose its own version, its name, the Scala version it was compiled against or the sbt version that built it, and the information is sitting right there in the build definition where the version is set. The usual workarounds are a hand-maintained constants file that drifts, a resource file generated by a task, or a build-time resource baked into the jar, each of which is a small mechanism that has to be remembered and can go stale.

This plugin removes the constant and generates a Scala object instead, with the values filled in from the build.

The generated object is a case object with a val per key, and each val carries a doc comment stating the value:

scala
package hello

/** This object was generated by sbt-buildinfo. */
case object BuildInfo {
  /** The value is "helloworld". */
  val name: String = "helloworld"
  /** The value is "0.1-SNAPSHOT". */
  val version: String = "0.1-SNAPSHOT"
  /** The value is "2.10.3". */
  val scalaVersion: String = "2.10.3"
  /** The value is "0.13.2". */
  val sbtVersion: String = "0.13.2"
  override val toString: String = "name: %s, version: %s, scalaVersion: %s, sbtVersion: %s".format(name, version, scalaVersion, sbtVersion)
}

Because it is generated source it is found under target, and the documentation gives the concrete path for a Scala 2.11 project as target/scala-2.11/src_managed/main/sbt-buildinfo. Nothing is checked in, and the generated file is compiled like any other source, which means the values are in the class file and reachable without any runtime machinery.

The licence is MIT, the primary language is Scala, the default branch is develop, and the recent releases are v0.13.2 from 2026-09-14, v0.13.1 from 2024-11-09 and v0.13.0 from 2024-11-03, with the last push to the repository on 2026-09-24. The repository itself is small, with a build.sbt, a src/ directory, a project/ directory for the plugin's own build and a notes/ directory.

Setting it up in a build, and the two keys that matter most

For sbt 1.x the plugin is added as a dependency in project/plugins.sbt:

scala
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "x.y.z")

and for older sbt the documentation points at different plugin versions, with 0.11.0 for sbt 0.13.6 and later and 0.3.2 for anything older than that.

The build definition then enables the plugin and lists the keys to expose:

scala
lazy val root = (project in file(".")).
  enablePlugins(BuildInfoPlugin).
  settings(
    buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion),
    buildInfoPackage := "hello"
  )

Those two settings are the whole configuration for the common case. buildInfoKeys is the list of values to generate, and the names in that list are the build's own keys, so name and version are the standard sbt project keys and scalaVersion and sbtVersion come from the same place.

buildInfoPackage sets the package the generated object lives in, and the documentation is clear about why it matters: changing the object name is optional, but to avoid name clash with other jars the package name should be unique. If two dependencies both generate a BuildInfo object in the default package, the class files collide, and the failure appears as a duplicate class error at link time rather than as anything the plugin could warn about. Using the package key is the fix, and the documentation points at it explicitly.

The object name is separately configurable with buildInfoObject := "Info", which changes the generated object name to object Info. That is the knob for the case where you want a different name but the same package discipline.

Custom keys, and the one rule about tasks

The plugin is not limited to the standard keys. buildInfoKeys can be extended with whatever you want to have in the BuildInfo object, and the documentation lists the mechanisms.

BuildInfoKey.map changes the generated field name and value, tuples add new fields with a value, and there are fields whose values are computed at build time. The example shows all three in one setting:

scala
buildInfoKeys ++= Seq[BuildInfoKey](
  resolvers,
  Test / libraryDependencies,
  BuildInfoKey.map(name) { case (k, v) => "project" + k.capitalize -> v.capitalize },
  "custom" -> 1234, // computed at project load time
  BuildInfoKey.action("buildTime") {
    System.currentTimeMillis
  } // re-computed each time at compile
)

The comments in that snippet distinguish the two evaluation times, and the difference matters. A tuple value is computed at project load time, once, when the build is loaded, and a value from an action is re-computed each time at compile. A build timestamp is only useful if it is the second kind, and a project name is fine either way.

There is a rule about tasks, and it is stated as a hard constraint: tasks can be added only if they do not depend on sourceGenerators, otherwise it will cause an infinite loop. The reason is structural rather than a quirk. The plugin generates source, and the source generation is a task, and if a key you want to include is itself a task that depends on source generation, then generating the source requires running the task which requires generating the source.

There is a second rule about how a TaskKey has to be written. A TaskKey must appear directly inside the setting macro, as in buildInfoKeys ++= Seq[BuildInfoKey](taskKey), and helper methods that take a TaskKey work if they are inline with an inline parameter:

scala
inline def named[A](inline key: TaskKey[A], name: String): BuildInfoKey =
  BuildInfoKey.map(key) { case (_, v) => name -> v }

The inline requirement is there because the macro has to see the key at compile time rather than receive it as an opaque value, which is a normal constraint for a build macro and the sort of thing that costs an afternoon if you hit it without the documentation.

The options, and the one that will bite a Scala 3 user

Beyond the keys, there is a set of BuildInfoOption values that change the shape of the generated object, and they are the reason to use this plugin rather than writing the object by hand.

BuildInfoOption.ToMap adds a toMap method returning a Map[String, Any] of the values, and BuildInfoOption.ToJson adds a toJson method, which is what you want when the values have to be serialised for a diagnostics endpoint or a log line.

BuildInfoOption.Traits takes the names of traits to mix into the generated object, for example BuildInfoOption.Traits("TestTrait1", "TestTrait2"). That is how you add behaviour to a generated object without giving up generation.

BuildInfoOption.BuildTime adds timestamp values, builtAtString with a formatted date and builtAtMillis with the epoch milliseconds.

BuildInfoOption.PackagePrivate makes the generated object package private, so it is visible inside the package and not outside it, which is the right default for an internal detail.

BuildInfoOption.ConstantValue declares all the vals in the object as final and without an explicit type annotation. The documentation says this is particularly useful if the values must be constants, for example if you need to assign them to annotation arguments. That is a real constraint rather than a style preference, because an annotation argument has to be a constant expression, and a val with an explicit type annotation and no final modifier is not one.

BuildInfoOption.ImportScalaPredef explicitly imports scala.Predef._ in the generated code, and there are two caveats attached to it that are worth reading together. The import is necessary only when using the compiler option -Yno-imports, and the generated import is not compatible when compiling with the Scala 3 compiler option -source:future, because import wildcards must be written as a star.

That last one is a genuinely useful warning, because the failure mode is a confusing compile error in generated code that the user never wrote, in a configuration they did not think about.

There is also a build number option, buildInfoBuildNumber, and the documentation notes a caveat: cross building against multiple Scala versions would each generate a new number.

Where sbt-buildinfo is the wrong tool

The first limit is that a generated Scala object is not a configuration file, and sometimes you want the latter. A resource file can be read at runtime, can be overridden in a deployment, and can be inspected without decompiling anything. A generated object is compiled into the class file, which is exactly what you want for a version stamp and exactly what you do not want for a value an operator might need to change without a rebuild.

The second is that the values are baked in at compile time, so they describe the build rather than the deployment. If you run the same artefact in two environments, the version it reports is the version it was compiled as, and if a build number is meant to identify a deployment rather than a build, the plugin is answering a different question. The documentation's own note about cross building and build numbers is a reminder that the identity of an artefact is more subtle than a counter.

The third is the scope of the generated object. It holds strings, and the constant value option exists precisely because you might need them to be usable as annotation arguments, but there is no support for a value that is not a compile-time constant, and there is no mechanism for reading a value at runtime from somewhere else. A configuration system that reads from the environment or a secret store is a different design.

The fourth is the build-level coupling. The keys come from the build definition, so the value of a field depends on the build, and a build that is set up differently, an aggregator, a multi-project build, produces different content. That is usually what you want and it is occasionally surprising, particularly when the value you expect comes from a parent build rather than the one you are reading.

And the fifth is the dependency cost for a small job. This is a plugin, in a project directory, that has to be kept compatible with your sbt version and your Scala version, and the documentation's own advice of pinning the version is the practical answer to that. For a library that needs one version string, a hand-written constant in a source file is less machinery, at the cost of being one more thing to remember to update. The plugin is worth it once you have more than one value, or once the value is the kind that is easy to forget.

Editorial conclusion

sbt-buildinfo is worth adding if you ship a library that needs to know its own version at runtime, because the values come from the build definition rather than from a constant somebody has to remember to update, and because the generated object is ordinary Scala that compiles into the artefact with no reflection or runtime dependency. It is the wrong tool if a resource file would do, and it is the wrong tool for a value that changes outside the build. Before you wire it in, check three things: that the keys you want are all build-time values, because the documentation is explicit that tasks can only be added if they do not depend on sourceGenerators, since otherwise the build loops forever; that your Scala version and sbt version combination is supported, since the plugin tracks sbt versions separately and the documentation points at different plugin versions for sbt 0.13.6 and later and for anything older; and that you do not need the values to be constants in a way the default output cannot satisfy, because the ConstantValue option is there precisely for that, and the ImportScalaPredef option is not compatible with the Scala 3 compiler option -source:future because import wildcards must be written as a star.

Frequently asked questions

How do I add sbt-buildinfo to an sbt project?

Add addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "x.y.z") to project/plugins.sbt for sbt 1.x, then enable the plugin in build.sbt with enablePlugins(BuildInfoPlugin) and set buildInfoKeys to the keys you want exposed, for example Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion). The generated source appears under target.

Can I put my own values into the generated BuildInfo object?

Yes. Extend buildInfoKeys with additional BuildInfoKey entries, using BuildInfoKey.map to change the generated field name and value, tuples for values computed at project load time, and BuildInfoKey.action for values re-computed at compile time. A TaskKey must appear directly inside the setting macro, and a task must not depend on sourceGenerators or the build will loop.

What options does sbt-buildinfo offer for the generated object?

There are options to add a toMap method, a toJson method, to mix in named traits, to include build timestamps, to make the object package private, to declare the values as final constants without type annotations, and to import scala.Predef._ explicitly. That last one is needed with the -Yno-imports compiler option and is not compatible with the Scala 3 -source:future option, because import wildcards must be written as a star.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sbt/sbt-buildinfo on GitHub
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/sbt-sbt-buildinfo.svg)](https://hysenlabs.com/projects/sbt-sbt-buildinfo)

Was this useful?