Haskell World logo Haskell WorldWrite code, build worlds
Developer Tools

Choosing a Haskell Code Formatter for Your Team

Your team keeps arguing about where the arrows go in a do block, and every pull request carries twenty lines of whitespace noise next to three lines of real change.

A developer's widescreen monitor showing neatly aligned Haskell source code beside a terminal window on a wooden desk

Your team keeps arguing about where the arrows go in a do block, and every pull request carries twenty lines of whitespace noise next to three lines of real change. Picking one Haskell code formatter and enforcing it ends that argument in an afternoon, but only if you choose the tool that matches how your team actually works.

I have moved four codebases onto a single formatter over the last few years, ranging from a 9,000 line internal library to a 140,000 line service monorepo. The choice itself took less time than the rollout, and the rollout is where most teams get hurt. This guide covers both.

The Haskell code formatter options that are still alive

The Haskell ecosystem has gone through several formatters, and some of the names you will find in old blog posts and Stack Overflow answers are no longer maintained. Here is the short list worth evaluating in 2026.

ToolConfigurationScopeStatus
OrmoluAlmost none by designWhole moduleActively maintained
FourmoluA fourmolu.yaml file with around 20 optionsWhole moduleActively maintained fork of Ormolu
stylish-haskellA .stylish-haskell.yaml fileImports, pragmas, alignmentMaintained, partial formatter
BrittanyConfig fileWhole moduleArchived, avoid for new projects
cabal-fmtMinimalOnly .cabal filesMaintained, pairs with any of the above

Ormolu came out of Tweag in 2019 with a blunt philosophy: one style, no knobs, and output that never changes the meaning of your code. It parses with the GHC parser itself, so any syntax your compiler accepts, Ormolu can format. It also reads the default extensions from your .cabal file, which matters if you lean on things like OverloadedRecordDot or BlockArguments.

Fourmolu is a fork that keeps the same engine but exposes settings: indentation width (4 by default, versus 2 in Ormolu), leading or trailing commas, where arrows sit in type signatures, how imports are grouped, and whether to respect existing blank lines. If you set every Fourmolu option to match Ormolu, the output is identical.

stylish-haskell is older and does less. It sorts and aligns imports, tidies language pragmas, trims trailing whitespace, and aligns record fields and case branches. Many teams ran it for years alongside manual formatting. Today it mostly makes sense as a bridge for a codebase that is not ready for a full rewrite.

How to choose between Ormolu and Fourmolu

For most teams the real decision is Ormolu or Fourmolu. I use three questions.

Does anyone on the team care deeply about style?

If the honest answer is no, pick Ormolu and move on. Zero configuration means zero future debates. A style file is a magnet for pull requests titled "small tweak to formatting," and each one reformats hundreds of files.

Is the existing code mostly 4 space indented?

Switching a 100,000 line codebase from 4 spaces to 2 touches nearly every line, which wrecks git blame for a long time. Fourmolu with its default 4 space indent can produce a much smaller first diff. On one service I measured it: Ormolu changed 61 percent of lines, Fourmolu with tuned options changed 23 percent.

Do you share code with other open source projects?

Many public libraries on Hackage now ship Ormolu output. If your developers move between your code and those projects every day, matching the common style lowers friction. If you are a closed shop, this matters less.

Wiring it into the editor and the build

A formatter only helps if it runs without anyone thinking about it. Set it up in three places.

  1. The editor. Haskell Language Server has a formatting provider setting. Set it to ormolu or fourmolu, and set the cabal formatting provider to cabal-fmt. Turn on format on save in your editor. If formatting silently does nothing, the cause is usually an HLS build that does not bundle the plugin you picked; our guide to troubleshooting Haskell Language Server covers how to confirm which plugins your binary includes.
  2. Pin the version. Formatter output changes between releases. Ormolu 0.7 and 0.5 produce different results on the same file. Pin the exact version in your project, through a cabal.project constraint, a Nix flake, or a tool manifest, and make HLS use that same version. Version drift is the number one cause of "it formats differently on my machine."
  3. Continuous integration. Both Ormolu and Fourmolu have a check mode that exits with a non-zero status if any file would change. Run it on every tracked .hs file in a job that takes under a minute. On our largest repo the check over roughly 1,800 modules took 38 seconds.

If you are setting up a fresh machine as part of this, the steps in setting up a Haskell development environment already include GHCup and HLS, so adding the formatter is one more line.

Why format on save alone is not enough

The common advice is "just turn on format on save and the problem solves itself." I disagree, and the reason is simple: not everyone uses the same editor, and not every edit goes through an editor. People resolve merge conflicts in a terminal, apply patches from scripts, and accept suggestions in a web review tool. Each of those paths skips your save hook.

Within two weeks of relying only on editor formatting, one team I worked with had 140 unformatted files again. The CI check is what makes the formatter a rule rather than a suggestion. Format on save is the convenience layer. CI is the enforcement layer. You need both.

There is a second, quieter benefit. Once formatting is mechanical, review comments stop being about layout. Reviewers who no longer write "nit: alignment" have more attention for logic, which ties into why code review comments get ignored when they are buried in trivia.

Rolling out a formatter on an existing codebase

The rollout is where teams lose a week if they are careless. Follow this order.

  1. Agree on the tool and pin the version in one small pull request with no reformatting.
  2. Pick a quiet moment, ideally right after a release, and ask people to merge or park open branches.
  3. Run the formatter across the whole repository in a single commit containing nothing else. Write a clear message such as "Apply Ormolu 0.7.7 to all modules."
  4. Add that commit hash to a .git-blame-ignore-revs file and tell everyone to set blame.ignoreRevsFile in their git config. GitHub and GitLab both read this file automatically in their blame views.
  5. Turn on the CI check in the same pull request or the one right after.

Long lived branches need special care. The easiest path is for the branch owner to run the same pinned formatter on their branch, commit that, then rebase. Conflicts drop to the lines they actually changed.

Handling the rare bad output

Ormolu supports disabling and re-enabling formatting around a region using special pragmas, which is useful for hand-aligned tables of constants or large literal lists. Use it sparingly. In our 140,000 line repo we ended up with exactly six such regions, all for data tables. If you find yourself adding them weekly, the problem is usually the code shape, not the formatter.

Frequently asked questions

Is Ormolu or Fourmolu better for a new Haskell project?

For a brand new project with no legacy style, Ormolu is the simpler choice because there is nothing to configure or debate. Fourmolu earns its place when you need 4 space indentation or have strong team preferences. Both share the same parsing engine, so correctness is equal.

Can a Haskell code formatter break my code?

Ormolu and Fourmolu check that the formatted output parses to the same syntax tree as the input and refuse to write the file if it does not. In practice this makes semantic breakage extremely rare. CPP heavy modules are the main exception, so test those carefully during rollout.

How do I format .cabal files?

Use cabal-fmt, which sorts and aligns fields, module lists, and dependencies in .cabal files. It is separate from your Haskell formatter, so run both in CI. HLS can call cabal-fmt on save if you set the cabal formatting provider.

SR
Sonja Reiter

Sonja Reiter contributed to three major open source projects and started writing documentation when she noticed most tooling guides were written by people who had never taught anyone to use them. She covers build tools, package ecosystems, and the invisible quality-of-life differences between development setups.

More posts by Sonja

More in Developer Tools

A GHCi session with type queries and a reloaded module Developer Tools

Using GHCi as Your Daily Haskell Workflow

Many Haskell beginners treat GHCi as a calculator they poke at occasionally, then do their real work by compiling full programs...

Sonja ReiterOct 15, 20265 min read
01doorganico 084goad 3hot8 4465456 after5internprogram ahandfulofblessings ahmadpc ahomcybersecurity allodialresources annodex antiquesandperiodcollectibles aotc asianworldkashmir beedlectrics bloggertemplatefree bluefeatheragency bodyweightsolution campaign08blog carfixadvisor catstreetcollective cbstvsports cnfanspurchasing committeeof500yearsofdignityandresistance covertopsdjs cs-kaiyuangame cuestacomunicaciontotal cxoutlet dancebetgame deskgearhub destinyvillastay detectmagazine di33 diamondsteelbuildings donmo dyanadropship emarsysfinance emimattl escapechan eskitarih evautoplay everythingweddings-orlando farmzone feedviewerapp foxstudiosscratch gamevui123 gleasongrade greetingsday hacklabasu iw-kaiyun jagoanproperti jilidskripsi joltbusinessfinance juegodepokergratis juegospucca kaeru-festival latestmehndidesigns lestanzediottaviano litemagazines localcommunityhero lustrsazan mariashebets menswheels newwebstechs1 nikkit ootheday oxigenvpn plurfitness prefetial probudgetcarpetcleaning psychicmrsmartin pyaten qvantor rejoignezlogis rexzbot riselayouts ruchin rwxqsgd5 sakaltime shibiaowang shop-bot skrtips strcoding tetapatrium topselller uneplanteartificielle vipertechcr webtech1223411 wo-kygaming yamaha-mekar yeknava yoybuysheets zenunwa yamaha-mekar jilidskripsi ankertoto ankertoto ankertoto ankertoto