Open-source project
sol/hpack avatar
sol/hpack

hpack: a package.yaml alternative to the Cabal file format

hpack: A modern format for Haskell packages

668 stars106 forksHaskellMIT

At a glance

What is it?
hpack is a format for describing Haskell packages, written as YAML instead of a Cabal file, and it exists to remove the boilerplate every package repeats. The generated .cabal file is still what Cabal consumes, and stack and cabal2nix read package.yaml natively. The one weak point in the documentation is that there is still no user guide.
Who is it for?
Adopt hpack if you maintain Haskell packages and are tired of repeating the same source directory, dependency and metadata lines in every .cabal file, and you are willing to treat the generated Cabal file as a build artefact rather than the source of truth. Skip it if your project has unusual Cabal configuration that hpack cannot express, since the escape hatch is a pass-through of raw Cabal fragments.
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 4 days ago.
What is it written in?
Mainly Haskell, 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

A package format designed to guess for you

hpack is a format for Haskell packages, described as a modern alternative to the Cabal package format that follows different design principles. The three principles are the whole argument, and they are short.

The first is not to require the user to state the obvious, and to make sensible assumptions by default. The second is to give the user 100% control when needed. The third is not to require the user to repeat things, which is the DRY principle applied to package metadata.

Those three sentences describe a different relationship with the build system from the one Cabal offers. A Cabal file states everything explicitly, which is a virtue in a build system and a nuisance in a package description that is mostly the same in every package. hpack takes the view that a package description is a document where most of the content is boilerplate, and a format that guesses well lets you delete the boilerplate.

The second principle is the counterweight, and it is stated just as strongly. The defaults are not a wall: when a default is wrong for your package, you can always write the value out, and when the format cannot express something, there is a documented way to pass Cabal configuration through verbatim.

The target audience is Haskell package maintainers, and the payoff shows up in the second file that a project no longer has to keep in sync by hand.

package.yaml in, .cabal out

Hpack packages are described in a file named package.yaml, and the relationship to the rest of the Haskell tooling is spelled out in the tool integration section.

Both cabal2nix and stack support package.yaml natively. For other build tools, the hpack executable can be used to generate a .cabal file from package.yaml. That is the whole architecture: the YAML file is the source, the Cabal file is derived, and the derivation is done by a tool that either runs as part of the build or on demand.

The examples section shows the same transformation applied to four real projects. Running hpack on the hpack repository's own package.yaml generates hpack.cabal, and the same holds for getopt-generics, for sensei and for base-orphans, with each project's package.yaml generating the correspondingly named .cabal file.

The last of those is the interesting case, because base-orphans is a package that has to support a wide range of compiler versions and configurations, and it is one that people routinely read the .cabal file of. Reading the generated file remains a valid debugging step, and hpack's own repository ships both, with hpack.cabal at the top level next to package.yaml, which is the pattern most projects end up with.

Whether the .cabal file is committed is a project decision the format does not make for you, and the hpack repository does both: it keeps the file in the tree and the workflow is visible in the repository layout.

The defaults that remove the boilerplate

The quick reference table maps each Hpack field to its Cabal equivalent, with the default and the version it appeared in, and the defaults are where the time saving actually comes from.

The homepage field defaults to the repository readme when a codeberg or github value is given, and bug-reports defaults to the repository issues page, with both the codeberg and github fields accepting an owner/repo or an owner/repo/subdir form. The maintainer field defaults to author. The license field is inferred from license-file, and license-file itself defaults to LICENSE if that file exists, which is the case in the overwhelming majority of packages. The version field defaults to 0.0.0 and build-type defaults to Simple, or to Custom if a custom-setup section exists.

The spec-version field is the one to read carefully. It is the minimum version of hpack required to parse the package description, and it is not a style preference, because it changes behaviour, as the next section shows.

Two other fields accept glob patterns, extra-doc-files since 0.21.2, extra-files since 0.38.1, and also extra-source-files and data-files. That is how a package stops listing every file it ships, which is the third design principle doing its work in the part of the file that grows fastest as a package adds assets.

The license field also accepts both SPDX license expressions and traditional Cabal license identifiers, with SPDX support since 0.29.0, so a modern identifier is not a reason to avoid the format.

spec-version decides how the Paths_ module is handled

Cabal generates a Paths_ module for every package, and how exactly hpack behaves about that module depends on the value of the spec-version field. If spec-version is explicitly specified and at least 0.36.0, the modern behaviour is used, otherwise hpack falls back to the legacy behaviour.

The modern behaviour is that if you want to use the Paths_ module for a component, you have to specify it explicitly under generated-other-modules:

yaml
library:
  source-dirs: src
  generated-other-modules: Paths_name # substitute name with the package name

The legacy behaviour exists for historic reasons, where hpack adds the Paths_ module to other-modules when generating a .cabal file. To prevent that, the README gives a conditional that turns the addition off:

yaml
library:
  when:
  - condition: false
    other-modules: Paths_name # substitute name with the package name

The trick is worth understanding rather than copying, because it uses the same conditional mechanism that Cabal itself uses, with a condition that is never true, so the field is present in the description and never applied.

This is the clearest illustration of the format's philosophy. The default is the historical behaviour, so that existing packages keep generating the same file they always did, and the new behaviour is opt-in through an explicit version statement. It is a format that changes its own rules only when you ask it to, and the version marker in your package.yaml is the switch.

There is still no user guide, only a reference

The README has a section with an unapologetic title: there is no user guide. The text explains that reference documentation is provided below, but introductory documentation is still lacking, and it points at the slides from the author's talk about hpack at the Singapore Haskell meetup, hosted at typeful.net/talks/hpack.

That is a real gap and it is worth naming for anyone evaluating the format. The reference documentation is thorough. The table of contents lists a handling of Paths_ modules section with modern and legacy behaviour, then a quick reference with sections for top-level fields, cabal-version, defaults, custom setup, common fields, library fields, executable fields, test fields, benchmark fields, flags, dependencies, conditionals and the 'Main' IO action, followed by file globbing, passing things to Cabal verbatim with objects, strings and lists of objects and strings, and a section called not repeating yourself.

So a reader who already knows what a Cabal file contains can look up any field. A reader who has never written a Cabal file has to start from the slides.

The passing things to Cabal verbatim section is the safety valve for the second design principle, since it is how you express configuration the format does not model. And the not repeating yourself section is where the file globbing and shared source directory behaviour is documented.

For a team adopting hpack, the practical reading order is the quick reference table first, then the verbatim section to know your escape hatch, and the slides for the mental model.

The repository, the executable and the maintenance record

The repository layout shows a normal Haskell project that also has to build and ship itself. At the top level there is hpack.cabal, package.yaml, Setup.lhs, cabal.project, stack.yaml and stack.yaml.lock, hie.yaml and .ghci for editor and IDE support, plus driver/, src/, test/, util/ and resources/ directories, and a get-hpack.sh script. There is also a CHANGELOG.md and the MIT LICENSE file.

The presence of both cabal.project and stack.yaml, plus the lock file for stack, is the two-toolchain reality of Haskell development, and the two build descriptions are both present because the tool needs to be built by the same means it describes.

The documentation also mentions integration points that a reader of a package format would want to know about. There is a section on the hpack executable itself, a section on Vim integration, a section on Stack support, and a section on binaries for use on Travis CI, which is the historical answer to the problem of getting a consistent hpack version onto a continuous integration machine.

The release record is steady rather than fast. Version 0.39.4 was published on 2026-05-03, 0.39.5 on 2026-05-04 and 0.39.6 on 2026-06-06, and the last push to the repository was on 2026-10-19. The package is available on Hackage and is listed in Stackage, both in the LTS snapshot and in the nightly build, and the project has no GitHub releases, so the Hackage and Stackage versions are the ones to track.

Editorial conclusion

Adopt hpack if you maintain Haskell packages and are tired of repeating the same source directory, dependency and metadata lines in every .cabal file, and you are willing to treat the generated Cabal file as a build artefact rather than the source of truth. Skip it if your project has unusual Cabal configuration that hpack cannot express, since the escape hatch is a pass-through of raw Cabal fragments. Before you convert a package, set spec-version explicitly, because it changes how the Paths_ module is handled, and read the quick reference table for the fields whose defaults you were previously spelling out. The last push was on 2026-10-19, the latest release is 0.39.6 from 2026-06-06, and the licence is MIT.

Frequently asked questions

What is hpack and what file does it use?

hpack is a format for Haskell packages, a modern alternative to the Cabal package format. Packages are described in a file named package.yaml, and the hpack executable generates a .cabal file from it.

Do cabal2nix and stack support package.yaml natively?

Yes. The README states that both cabal2nix and stack support package.yaml natively, and for other build tools the hpack executable can be used to generate a .cabal file from package.yaml.

What does the spec-version field do in a package.yaml?

It is the minimum version of hpack required to parse the package description, and it also selects behaviour. If spec-version is at least 0.36.0 the modern Paths_ module behaviour applies, otherwise hpack falls back to the legacy behaviour of adding the module to other-modules.

How do I express Cabal settings that hpack does not model?

The reference documentation has a section on passing things to Cabal verbatim, covering objects, strings and lists of objects and strings, which is the documented escape hatch for configuration the format does not express directly.

Official sources

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

Was this useful?