How to Create a Flask App: A Step-by-Step Guide for Windows, macOS and Linux
Install Python, set up a virtual environment, and build your first Flask 3.1 app with routes, Jinja templates, static files, forms and a JSON API, explained in plain English for every operating system.

Mohit Malaya Nandy
Software Engineer & Product Builder

On this page
- What is Flask?
- 1. Install Python
- 2. Create a project folder
- 3. Virtual environment
- 4. Install Flask
- 5. Your first Flask app
- 6. Run the development server
- 7. Routes
- 8. Jinja templates
- 9. Static files
- 10. Forms (GET and POST)
- 11. JSON API
- 12. Custom 404 page
- 13. Project structure
- 14. Run in production
- Common errors
- FAQ
- Official references
Flask is a lightweight Python web framework. It gives you the basics you need to build a website or an API (URLs, pages, forms and JSON) without forcing a big project structure on you. That makes it one of the easiest ways to start web development with Python.
In this step-by-step guide you will set up Python on Windows, macOS or Linux, create a Flask project, and build a small app with several pages, an HTML template, a stylesheet, a form and a JSON API. Every step explains why you are doing it, not just what to type. All commands follow the current official Flask 3.1 documentation and Python setup guides. Wherever a command differs between operating systems, pick yours from the tabs above the code; your choice is remembered for the whole page.
What is Flask and why use it?#
Flask is called a micro-framework. "Micro" does not mean it is only for small projects. It means the core stays small and simple, and you add only the features you need. When you install Flask, it brings a few trusted libraries with it:
| Library | What it does for you |
|---|---|
| Werkzeug | Handles HTTP requests and responses (the WSGI layer between your app and the server). |
| Jinja | The template engine that turns HTML templates into real pages. |
| MarkupSafe | Escapes untrusted text so users cannot inject HTML or scripts. |
| ItsDangerous | Signs data securely. Flask uses it to protect the session cookie. |
| Click | Powers the flask command you will use in the terminal. |
| Blinker | Adds support for signals (events your app can react to). |
This list comes from the official Flask installation guide. You do not need to install any of them yourself; pip install Flask does it for you.
Step 1: Install Python#
First, check whether Python is already installed. Open a terminal and run the command for your system:
python --versionIf you see Python 3.9 or newer, you can skip to Step 2. Flask 3.1 supports Python 3.9+, but the Flask team recommends the latest version. Otherwise, install Python for your system below.
Windows: use the Python Install Manager
The Python documentation now recommends the Python Install Manager on Windows. The old full .exe installer is deprecated since Python 3.14 and will not be produced for Python 3.16 or later (Python docs). Install it in one of these ways:
- From the Microsoft Store: click Install.
- From python.org/downloads: download the installer and double-click it.
- From PowerShell:
winget install 9NQ7512CXL7T -e --accept-package-agreements --disable-interactivity
It gives you the python, py and pymanager commands. Open a new PowerShell window and run py list to see your installed Python versions.
macOS: python.org installer or Homebrew
macOS does not include a current Python for development. Download the macOS installer from python.org/downloads and run it (Using Python on macOS), or, if you use Homebrew:
brew install pythonLinux: use your package manager
Most Linux distributions already include Python 3. You may still need the venv and pip packages (Using Python on Unix platforms):
# Debian / Ubuntu
sudo apt update
sudo apt install python3 python3-venv python3-pip
# Fedora
sudo dnf install python3 python3-pipStep 2: Create a project folder#
Every Flask app lives in its own folder. Create one and move into it. This command is the same on every system:
mkdir my_flask_app
cd my_flask_appOpen this folder in your editor. In VS Code, run code . from the same terminal.
Step 3: Create and activate a virtual environment#
A virtual environment is a private folder of Python packages for one project. Without it, every project on your computer shares the same packages, and upgrading a library for one project can break another. The Flask docs recommend a virtual environment for both development and production.
Create one called .venv using Python's built-in venv module:
python -m venv .venvNow activate it so that Python and pip use this folder:
.venv\Scripts\activateOn Windows, this works in both PowerShell and Command Prompt. Your prompt now starts with (.venv), which tells you the environment is active. You need to activate it again every time you open a new terminal for this project. To leave it, type deactivate.
Step 4: Install Flask#
With the virtual environment active, install Flask with pip, Python's package installer. From here on, the commands are the same on every operating system:
pip install FlaskCheck that it installed correctly:
flask --versionNow save the exact package versions to a file called requirements.txt. Anyone (including you, on another computer or a server) can recreate the same setup later with pip install -r requirements.txt:
pip freeze > requirements.txtStep 5: Write your first Flask app#
Create a file called app.py in your project folder and add this code:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "<h1>Hello, Flask!</h1>"Here is what each line does:
from flask import Flaskimports theFlaskclass.app = Flask(__name__)creates your application.__name__tells Flask where your code lives, so it can find yourtemplatesandstaticfolders.@app.route("/")is a decorator. It connects the URL/(your home page) to the function below it.home()is a view function. Whatever it returns is sent to the browser. A string is sent as HTML by default.
Step 6: Run the development server#
Start the app in debug mode:
flask --app app run --debugYou should see output like this:
* Serving Flask app 'app'
* Debug mode: on
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!Open http://127.0.0.1:5000 in your browser. You will see Hello, Flask!. Press Ctrl+C in the terminal to stop the server.
What the command means
--app apptells Flask to load the fileapp.py. Because your file is namedapp.py, Flask would find it on its own, but being explicit avoids surprises.runstarts the built-in development server.--debugturns on two helpful features: the reloader restarts the server every time you save a file, and the debugger shows a detailed error page in the browser when something goes wrong.
Optional: save your settings in .flaskenv
Typing --app app --debug every time gets repetitive. Install python-dotenv and Flask will read settings from a .flaskenv file automatically (Flask CLI docs):
pip install python-dotenvFLASK_APP=app
FLASK_DEBUG=1
FLASK_RUN_PORT=5000Now flask run is enough. Use .flaskenv for public settings like these, and a separate .env file (never committed to Git) for secrets.
Step 7: Add more pages with routes#
A route connects a URL to a function. Add an About page and a page that greets a user by name:
from flask import Flask
from markupsafe import escape
app = Flask(__name__)
@app.route("/")
def home():
return "<h1>Hello, Flask!</h1>"
@app.route("/about")
def about():
return "<h1>About</h1><p>This app is built with Flask.</p>"
@app.route("/hello/<name>")
def hello(name):
return f"<h1>Hello, {escape(name)}!</h1>"
@app.route("/post/<int:post_id>")
def show_post(post_id):
return f"<h1>Post number {post_id}</h1>"Save the file. Because debug mode is on, the server reloads by itself. Try /about, /hello/Priya and /post/42.
URL variables and converters
The part in angle brackets, like <name>, is a variable. Flask passes it to your function. You can add a converter to control what it accepts:
| Converter | Accepts | Example |
|---|---|---|
string | Any text without a slash (the default) | /hello/<name> |
int | Positive whole numbers | /post/<int:post_id> |
float | Positive decimal numbers | /price/<float:amount> |
path | Like string, but slashes are allowed | /files/<path:subpath> |
uuid | UUID strings | /order/<uuid:order_id> |
Step 8: Use HTML templates with Jinja#
Writing HTML inside Python strings quickly gets messy. Flask uses the Jinja template engine so you can keep HTML in separate files. Flask looks for templates in a folder named templates next to app.py.
Create a base layout
A base template holds the parts every page shares, like the <head> and navigation. Other pages extend it and fill in the blocks. This is called template inheritance.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}My Flask App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('home') }}">Home</a>
<a href="{{ url_for('about') }}">About</a>
<a href="{{ url_for('contact') }}">Contact</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>Create a page that extends it
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome, {{ name }}!</h1>
<p>Here are some things you can learn next:</p>
<ul>
{% for topic in topics %}
<li>{{ topic }}</li>
{% endfor %}
</ul>
{% endblock %}{% extends "base.html" %}
{% block title %}About{% endblock %}
{% block content %}
<h1>About</h1>
<p>This app is built with Flask.</p>
{% endblock %}Render the templates from Python
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
topics = ["Routing", "Templates", "Forms", "Databases"]
return render_template("index.html", name="Developer", topics=topics)
@app.route("/about")
def about():
return render_template("about.html")The three pieces of Jinja syntax you will use most:
| Syntax | Meaning |
|---|---|
{{ name }} | Print a value. It is escaped automatically. |
{% for … %} / {% if … %} | Logic: loops and conditions. |
{% block … %} / {% extends … %} | Template inheritance. |
url_for('home') builds a link from the function name, not the URL. If you later change /about to /about-us, every link updates by itself.
Step 9: Add CSS, JavaScript and images#
Files that never change, such as CSS, JavaScript and images, are called static files. Put them in a folder named static; Flask serves it at /static. The base template already links static/style.css, so create it:
body {
font-family: system-ui, sans-serif;
max-width: 42rem;
margin: 2rem auto;
padding: 0 1rem;
line-height: 1.6;
}
nav a {
margin-right: 1rem;
}Always link static files with url_for('static', filename='…') instead of typing the path. It keeps working even if your app is later served from a sub-folder.
Step 10: Handle a form (GET and POST)#
By default, a route only answers GET requests (opening a page). To receive form data, a route also needs POST. Flask has shortcuts, @app.get and @app.post, that keep each part simple:
from flask import Flask, render_template, request, redirect, url_for, flash
app = Flask(__name__)
app.config["SECRET_KEY"] = "change-me" # needed for flash(); see the note below
@app.get("/contact")
def contact():
return render_template("contact.html")
@app.post("/contact")
def contact_submit():
name = request.form.get("name", "").strip()
if not name:
flash("Please enter your name.")
return redirect(url_for("contact"))
flash(f"Thanks, {name}! Your message was received.")
return redirect(url_for("contact")){% extends "base.html" %}
{% block title %}Contact{% endblock %}
{% block content %}
<h1>Contact</h1>
{% for message in get_flashed_messages() %}
<p><strong>{{ message }}</strong></p>
{% endfor %}
<form method="post">
<label for="name">Your name</label>
<input id="name" name="name" required>
<button type="submit">Send</button>
</form>
{% endblock %}How it works:
- Opening
/contactsends a GET request, socontact()shows the form. - Submitting the form sends a POST request, so
contact_submit()runs. request.form.get("name")reads the field. Using.get()avoids an error when the field is missing.flash()stores a one-time message, andredirect()sends the browser back to the form. Redirecting after a POST stops the browser from re-submitting when the user refreshes.
Step 11: Return JSON for an API#
If a view function returns a Python dict or list, Flask converts it to JSON automatically. This is all you need for a simple API:
@app.get("/api/status")
def api_status():
return {"status": "ok", "framework": "Flask"}Open /api/status and you will see the JSON response. You can call it from JavaScript with fetch("/api/status").
Step 12: Show a friendly 404 page#
When someone visits a URL that does not exist, Flask shows a plain "Not Found" page. Replace it with your own using an error handler:
@app.errorhandler(404)
def page_not_found(error):
return render_template("404.html"), 404{% extends "base.html" %}
{% block title %}Page not found{% endblock %}
{% block content %}
<h1>Page not found</h1>
<p><a href="{{ url_for('home') }}">Go back home</a></p>
{% endblock %}The , 404 after render_template sets the HTTP status code. Without it, Flask would return 200 ("OK"), and search engines could index your error page as a real page.
Step 13: Check your project structure#
Your finished project should look like this:
my_flask_app/
├── .venv/ # virtual environment (do not commit)
├── static/
│ └── style.css
├── templates/
│ ├── 404.html
│ ├── about.html
│ ├── base.html
│ ├── contact.html
│ └── index.html
├── .env # secrets (do not commit)
├── .flaskenv # public Flask settings
├── .gitignore
├── app.py
└── requirements.txtIf you use Git, create a .gitignore so the virtual environment, caches and secrets stay out of your repository:
.venv/
__pycache__/
*.pyc
.env
instance/Remember to update requirements.txt after installing new packages: pip freeze > requirements.txt.
Step 14: Run Flask in production#
The Flask docs are clear: do not use the development server in production. It is not designed to be secure, stable or efficient (Deploying to Production). Instead, use a WSGI server, a program built to run Python web apps reliably. Which one to choose depends on your system:
| System | Recommended server | Why |
|---|---|---|
| Windows | Waitress | Pure Python, no compiling, supports Windows directly. |
| macOS / Linux | Gunicorn | The most common choice on Linux servers. It does not run on Windows. |
pip install waitress
waitress-serve --host 127.0.0.1 app:appapp:app means "from the file app.py, use the variable app". Waitress starts on http://127.0.0.1:8080, and Gunicorn on http://127.0.0.1:8000. For Gunicorn, -w 4 starts four worker processes so it can handle several requests at once. Debug mode is off in both, which is what you want.
Where to host your Flask app
To put your app on the internet without managing your own server, the Flask docs list these hosting platforms, all with official Python or Flask guides:
- PythonAnywhere, which is beginner-friendly.
- Google Cloud Run and Google App Engine
- AWS Elastic Beanstalk
- Microsoft Azure App Service
Before you deploy, check this list:
- Debug mode is off.
SECRET_KEYis a long random value loaded from an environment variable.requirements.txtis up to date..envis not in your Git repository.
Common errors and how to fix them#
| Error | Cause | Fix |
|---|---|---|
| Windows: 'python' or 'py' is not recognized | Python is not installed, or its app execution aliases are turned off. | Install the Python Install Manager. Then open Settings → Apps → Advanced app settings → App execution aliases and turn on the Python aliases. Open a new terminal. |
| Windows: py: can't open file … | The old, deprecated Python launcher is taking priority over the new py command. | Uninstall “Python launcher” from Installed apps (recommended by the Python docs). |
| Windows: running scripts is disabled on this system | PowerShell's execution policy blocks Activate.ps1. | Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser. |
| No module named 'flask' | The virtual environment is not active, so Python cannot see Flask. | Activate it again (.venv\Scripts\activate on Windows, . .venv/bin/activate on macOS/Linux), then try again. |
| Linux: command 'python' not found | Many Linux systems only provide python3. | Use python3 outside the virtual environment. Inside an active environment, python works. |
| Linux: ensurepip is not available | The venv module is installed separately on Debian and Ubuntu. | Run sudo apt install python3-venv, then create the environment again. |
| Address already in use / OSError: [WinError 10013] | Another program is already using port 5000. On macOS this is often the AirPlay Receiver. | Use another port: flask --app app run --debug --port 5001. On a Mac you can also turn off AirPlay Receiver in System Settings. |
| jinja2.exceptions.TemplateNotFound | Flask cannot find the template file. | The folder must be named exactly templates and sit next to app.py. |
| Error: Could not locate a Flask application | Flask does not know which file holds your app. | Pass --app app, or add FLASK_APP=app to .flaskenv. |
Frequently asked questions#
Which Python version do I need for Flask?
Flask 3.1 supports Python 3.9 and newer, but the Flask team recommends using the latest Python release. The steps are the same on Windows, macOS and Linux.
Do I need a virtual environment for a Flask project?
It is not required, but it is strongly recommended. A virtual environment keeps each project's packages separate, so upgrading a library for one project cannot break another.
Why does PowerShell say running scripts is disabled when I activate the virtual environment?
PowerShell's default execution policy blocks scripts such as Activate.ps1. Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser once, then activate the environment again.
Can I use flask run in production?
No. The flask run command starts a development server that is not designed to be secure, stable or efficient. Use a production WSGI server instead: Waitress on Windows, or Gunicorn on macOS and Linux.
What is the difference between flask run and python app.py?
flask run uses Flask's command line interface, which finds your app, loads .env and .flaskenv files and supports options such as --debug and --port. python app.py only works if your file calls app.run() itself. The Flask documentation recommends the flask command.
Why is port 5000 already in use on my Mac?
On macOS Monterey and later, the AirPlay Receiver uses port 5000. Turn it off in System Settings, or run Flask on another port with flask --app app run --debug --port 5001.
How do I change the port Flask runs on?
Add the --port option, for example flask --app app run --debug --port 5001, or set FLASK_RUN_PORT in a .flaskenv file.
Next steps#
You now have a working Flask app with pages, templates, static files, a form, a JSON API and a custom error page. Here is where to go next:
- Follow the official Flask tutorial, which builds a complete blog with a database and user login.
- Add a database with Flask-SQLAlchemy.
- Validate forms and protect them from CSRF attacks with Flask-WTF.
- Split a growing app into sections with Blueprints.
Official references#
Every command and recommendation in this guide comes from these sources:
- Using Python on Windows, Python documentation
- Using Python on macOS and on Unix platforms, Python documentation
- venv: creation of virtual environments, Python documentation
- Installation, Flask 3.1 documentation
- Quickstart, Flask 3.1 documentation
- Command Line Interface, Flask 3.1 documentation
- Deploying to Production, Waitress and Gunicorn, Flask 3.1 documentation
- Template Designer Documentation, Jinja
- About Execution Policies, Microsoft PowerShell documentation
Found this useful? You can support my writing on GitHub Sponsors or with a coffee.


