Using Prettier with a Pre-commit Hook
Table of Contents
- The Problem
- Making the case for Prettier
- Demo with Prettier Playground
- Getting Started
- Copy Example Files
- Confirming Husky Setup Hooks
- Seeing Prettier in Action Locally
- Using Prettier with Husky
- Using Prettier with Husky and lint-staged
- Ignoring and Targeting Specific Files
- Sharing a Prettier config
- Run Prettier Once on All Source Files
- Conclusion

The Problem
It’s difficult for everyone to commit to using a new tool. It’s not maintainable to ask every team member to install a new plugin. This is generally the problem with any new tools you want your team to use. If it forces developers to change the way they work at a cost at their velocity and productivity, then it becomes difficult to get people to use something new.
Making the case for Prettier
The Prettier website already makes a great case for why you should use it. But I found that people I work with now and in the past have resonated most with these reasons:
- Setting it up is easy — one person can do it for everyone
- Integrates with ESLint (more on that in another post)
- Integrates with Git Hooks via Husky
- Supports formatting for lots of file types: CSS, JSON, Markdown, and more
In other words, you can make a case to your team where you say,
“Hey, let’s use this thing that autoformats all of our code for us. It’ll make all of our code reviews easier because code styling will be more consistent. I can set it up for everyone and no one has to change the way they work to make use of it.”
Demo with Prettier Playground
Showing is usually better than telling.
If you have teammates who may not be familiar with Prettier, you can quickly demo it with the Prettier Playground. For good measure, here’s some badly formatted JavaScript and CSS for you to play with too.
Getting Started
The rest of this article will be a guided tutorial where we will cover the following:
- How to setup Prettier with a pre-commit hook
- Understanding the different tools used to make this work
Assuming you’re using git
, node
and npm
(or yarn
), then you should be good to continue.
You can start with a barebones project or work in an existing project.
In this article, we’ll work off of the barebones project. Clone it and cd
into it.
git clone git@github.com:hellobrian/every-new-project.git prettier-example
cd prettier-example
# remove commits from cloned
rm -rf .git && git init project
Here are the packages we’re going to use:
- prettier: An opinionated and automatic code formatter
- lint-staged: A tool to run linters against staged git files
- husky: Gives you easy access to git hooks via npm scripts
Use npm
or yarn
to install as devDependencies
npm i prettier lint-staged husky -D
You’ll have a package.json file that looks like this.
{
"name": "prettier-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"husky": "^1.2.1",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3"
}
}
Copy Example Files
We’ll need some example code to work with. Let’s reuse the example.js and example.css files.
Make a new src directory, then copy and paste example files to it.
mkdir src && touch src/example.{js,css}
Confirming Husky Setup Hooks
Quick aside: since we installed Husky, you can confirm that it created new git
hooks for you by peeking into your project’s .git files.
ls .git/hooks
less .git/hooks/pre-commit
Husky won’t overwrite any existing hooks that may already exist in your project. You should see some kind of console output in your terminal if Husky was unable to set things up correctly.
Seeing Prettier in Action Locally
Before we write any npm scripts, we can see Prettier in action using npx
. In your terminal do the following:
npx prettier --write src/**/*.{js,css}
- The
--write
flag tellsprettier
to format files in place - The
src/**/*.{js,css}
pattern will target all JavaScript and CSS files in the src directory and subdirectories.
Notice the change to our files:
- spacing is normalized
- indents are fixed
- semicolons are added consistently
- no more mixing of single and double quotes
example.js
$("#speedPercent").on("input", (event) => {
$(".output").value = event.target.value + "%";
});
$("#grid").on("click", (event) => {
if (event.target && event.target.matches("button.banana")) {
const points = parseInt(event.target.dataset.points, 10);
state = { ...state, score: state.score + points };
setScoreInnerHTML(state);
const span = event.target.querySelector("span");
span.classList.add("exit-animation");
span.on("animationend", () => {
event.target.parentNode.removeChild(event.target);
});
}
});
const mutationObserver = observer(state);
mutationObserver.observe($("#grid"), {
attributes: false,
childList: true,
subtree: true,
});
example.css
html {
box-sizing: border-box;
font-size: 16px;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.banana > span:after {
content: attr(data-points) "pts";
font-size: 0.875rem;
position: absolute;
top: 50%;
left: -40%;
background-color: var(--white-50);
padding: 2px 10px;
color: rgba(1, 1, 1, 1);
border-radius: 4px;
}
Using Prettier with Husky
Let’s move that prettier
script into an npm script and make it work with the “pre-commit” git hook.
{
"scripts": {
"prettier": "prettier --write src/**/*.{js,css}"
},
"husky": {
"hooks": {
"pre-commit": "npm run prettier"
}
}
}
This setup will run npm run prettier
whenever you run git commit
, which makes Husky the piece of this setup that makes it easy for everyone on your team to use Prettier.
But notice that all the files get reformatted when we really just want staged files to get the Prettier treatment.
Keep reading 🐶
Using Prettier with Husky and lint-staged
{
"scripts": {
"prettier": "prettier --write src/**/*.{js,css}"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"src/**/*.{js,css}": ["prettier --write", "git add"]
}
}
}
Adding lint-staged here let’s us run Prettier on staged files only so that the files you actually commit to git will be affected.
Let’s breakdown how to read the lint-staged config.
- The target pattern becomes a key in the “linters”` object.
- The value is now an array of commands that will get executed where
prettier --write
andgit add
get called one after the other. - The
git add
command gets added since theprettier
command will reformat all the staged files according to the target pattern and will re-add them right before you write your commit message
Ignoring and Targeting Specific Files
There are a lot of ways for Prettier to ignore files
So far, we’ve been targeting src files only, and this will inherently ignore other directories.
{
"lint-staged": {
"linters": {
"src/**/*.{js,css}": ["prettier --write", "git add"]
}
}
}
You can also add specific files too, like some common files outside of src.
{
"lint-staged": {
"linters": {
"src/**/*.{js,css}": ["prettier --write", "git add"]
"webpack.config.js": ["prettier --write", "git add"],
"package.json": ["prettier --write", "git add"],
"*.md": ["prettier --write", "git add"]
}
}
}
Heck, you can even add an ignore
key here too because…safety!
{
"lint-staged": {
"linters": {
"src/**/*.{js,css}": ["prettier --write", "git add"],
"webpack.config.js": ["prettier --write", "git add"],
"package.json": ["prettier --write", "git add"],
"*.md": ["prettier --write", "git add"]
},
"ignore": ["node_modules", "dist", "package-lock.json"]
}
}
Or you can create a .prettierignore file at the root of your project, which works just like a .gitignore file:
node_modules
dist
package-lock.json
Sharing a Prettier config
If you don’t want to use the default config, you can create a .prettierrc file with your own config and overrides.
Here I specified in overrides
that Markdown files should use double quotes, but all other files targeted should use single quotes.
{
"arrowParens": "always",
"bracketSpacing": true,
"jsxBracketSameLine": true,
"jsxSingleQuote": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"overrides": [
{
"files": "*.md",
"options": {
"singleQuote": false
}
}
]
}
Run Prettier Once on All Source Files
Once you have all of this setup, you can run Prettier on all the source files you want to target so that everything is consistently formatted in your pull request. Doing this will ensure that all of your teammates will get the formatting changes once so that future pull requests from teammates don’t have unneeded diffs, which can create a lot of noise during code review.
Conclusion
That’s about as much as I can elaborate on Prettier, stay tuned for ESLint integration in another article.
You can find the finished example code here on my GitHub.
Also, this article was recently featured here:
Thanks for reading!