Pyrefly is a fast, powerful static type checker built to work seamlessly with Python. It combines high-speed type analysis with rich IDE integration, helping you write better code through instant feedback and comprehensive error detection.
This guide walks you through setting up and using Pyrefly in your Python projects.
Prerequisites
Before getting started with Pyrefly, ensure you have:
- A recent version of Python installed (Python 3.9 or higher is recommended).
- Familiarity with Python type annotations.
Step 1 — Installing Pyrefly
In this section, you'll install Pyrefly and verify the installation works correctly.
Pyrefly releases new versions every Monday on PyPI, with additional releases throughout the week for new features and fixes. You can install it using several Python package managers.
For this guide, you'll use pip, but uv, poetry, pixi, and conda work equally well.
Before installing Pyrefly, create a dedicated directory for your project and move into it:
All remaining commands in this guide will be run from inside this directory. Before installing Pyrefly, create and activate a virtual environment:
Now, install Pyrefly with pip:
After installation completes, confirm Pyrefly is available by checking its version:
A successful installation displays the version number:
With Pyrefly installed, you can move on to initializing your project configuration.
Step 2 — Initializing your project
Now that Pyrefly is ready, you'll set up a basic project configuration. Pyrefly provides an initialization command that creates the configuration file automatically.
Navigate to your project directory and run:
This command either updates your existing pyproject.toml with Pyrefly settings or creates a new pyrefly.toml file in your project directory. If you're migrating from another type checker like Mypy or Pyright, pyrefly init attempts to convert your existing configuration.
Pyrefly also runs an initial pyrefly check as part of pyrefly init. If your project doesn't contain any Python files yet, you'll see an error like the one above—this is expected and will go away once you add Python or notebook files that match the configured patterns.
The initialization creates a minimal working configuration. By default, your pyrefly.toml might look like this:
The project-includes setting tells Pyrefly which files to analyze:
**/*.py*matches all Python files (including.pyand.pyi) anywhere in your project.**/*.ipynbmatches all Jupyter notebooks.
These patterns are recursive and are evaluated relative to your project root. If you prefer to limit analysis to a specific directory like src, you can update the config to:
With configuration in place, you can start type checking your code.
Step 3 — Running your first type check
In this step, you'll create a simple Python file with intentional type errors, then use Pyrefly to detect them.
Create a directory named src and add a file called main.py:
The function calculate_total expects a float and an integer. Passing a string as the first argument breaks the type contract.
Run Pyrefly on your source directory:
Pyrefly analyzes your code and reports the type mismatch:
The error message pinpoints exactly where the problem occurs and explains what went wrong. This immediate feedback prevents bugs from reaching production.
To see a summary of error types in your project, add the --summarize-errors flag:
This displays both individual errors and statistics about error categories across your codebase.
Next, you'll learn how to configure Pyrefly's behavior more precisely.
Step 4 — Configuring Pyrefly's type checking
In this step, you'll explore how Pyrefly catches potential runtime errors through its type analysis.
Update your main.py to demonstrate Pyrefly's capabilities:
The function find_user returns Optional[str], meaning it can return either a string or None. Calling upper() directly on this result without checking for None creates a potential crash.
Run Pyrefly:
Pyrefly identifies the issue:
The error catches a potential runtime failure—calling upper() on None would crash your application if find_user returns None.
Fix the issue by adding a null check:
Run Pyrefly again:
With proper null checking, your code passes Pyrefly's verification. This pattern of handling Optional types correctly prevents runtime errors before they happen. You can explore additional Pyrefly configuration options to customize the checker's behavior for your project.
Step 5 — Working with advanced type features
After establishing basic type checking, you can leverage Python's more sophisticated type system features. Pyrefly handles generic types, protocols, and type narrowing efficiently.
Using generics for reusable code
Generics let you write functions and classes that work with multiple types while maintaining type safety. Update your main.py to include a generic container:
Run Pyrefly:
Pyrefly tracks the generic type parameter throughout your code. When you create Storage(42), it knows this instance works with integers, so passing a string to set_value triggers an error.
Working with protocols for structural typing
Protocols define interfaces without requiring explicit inheritance. They're useful when you want duck typing with type safety.
Add a protocol to your file:
Running Pyrefly reveals the protocol violation:
BrokenWidget lacks the render() method required by the Renderable protocol. Pyrefly catches this mismatch even though there's no explicit inheritance relationship.
These advanced features give you flexibility without sacrificing type safety. As your application grows, Pyrefly scales with you, maintaining fast checking times even on large codebases.
When introducing Pyrefly to an existing project, you might encounter hundreds or thousands of type errors. Fixing them all immediately isn't always practical.
Pyrefly provides a way to mark existing errors as known issues, giving you a clean starting point. You can then address them gradually.
Create a file with several type issues:
Running Pyrefly shows multiple errors:
Rather than fixing everything immediately, suppress these errors:
Pyrefly adds # pyrefly: ignore comments next to each error:
After suppression, running Pyrefly again shows a clean result:
New type errors will still be caught, but existing issues are temporarily silenced. As you refactor code, remove the ignore comments and add proper type annotations.
If your code formatter moves or duplicates the ignore comments, clean them up:
This removes any ignore comments that no longer correspond to actual errors, keeping your codebase clean.
Final thoughts
By this point, you’ve seen how to install Pyrefly, initialize a project, and run your first checks against both simple examples and more realistic code. You’ve configured which files Pyrefly should analyze, handled common issues like Optional values, and explored more advanced types such as generics and protocols. You’ve also learned how to gradually introduce Pyrefly into existing code by suppressing and later cleaning up legacy errors.
From here, you can start applying Pyrefly to your real projects: tighten your configuration, integrate it into your editor and CI, and incrementally raise the level of type safety in your codebase. As your project grows, Pyrefly can grow with it, giving you fast, actionable feedback that helps keep your Python code reliable and easier to maintain over time.