Flask Static Files: CSS, JavaScript, and Assets
A Flask application sends more than HTML. After receiving a page, the browser usually makes additional requests for the CSS, JavaScript, images, fonts, and favicon referenced by that HTML. These are called static files because Flask does not need to process them as templates.
By default, Flask serves assets from static/. Subfolders inside it keep CSS, JavaScript, and images from becoming mixed together.
Organize Assets by Type
Add static next to app.py and templates:
# File: folder-structure.txt
flask-first-route/
├── app.py
├── templates/
│ └── home.html
└── static/
├── css/
│ └── app.css
├── js/
│ └── app.js
└── images/
└── logo.png
This structure is not mandatory, but consistent folder names make assets easier to find as the project grows.
Connect Assets from a Template
Use url_for("static", filename="...") to build an asset URL. The filename value is relative to static, so the word static should not appear twice.
<!-- File: templates/home.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flask Notes</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
<script src="{{ url_for('static', filename='js/app.js') }}" defer></script>
</head>
<body>
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Flask Notes">
<h1>Flask Notes</h1>
<button id="theme-button" type="button">Switch theme</button>
</body>
</html>
Flask generates a URL such as /static/css/app.css. Using url_for keeps the template independent from a hardcoded static path and allows it to keep working if that configuration changes.
Add CSS and JavaScript
/* File: static/css/app.css */
body {
font-family: system-ui, sans-serif;
margin: 2rem;
background: #ffffff;
color: #172033;
}
body.dark {
background: #172033;
color: #ffffff;
}
The JavaScript adds or removes the dark class on <body>:
// File: static/js/app.js
const themeButton = document.querySelector("#theme-button");
themeButton.addEventListener("click", () => {
document.body.classList.toggle("dark");
});
The defer attribute tells the browser to run the script after parsing the HTML. The button therefore exists by the time querySelector() runs.
One Page Can Trigger Several Requests
When / opens, Flask first sends the rendered home.html. The browser reads that HTML, finds three asset URLs, and requests the CSS, JavaScript, and image separately. A single page view almost always involves several requests.
You can inspect the process in the Network tab of browser DevTools. Status 200 means a file loaded successfully, while 404 usually points to a filename or path mismatch.
When CSS Changes Do Not Appear
Browsers cache static files to make later page loads faster. As a result, a CSS change may not appear immediately even when the file is correct. Try a hard refresh or temporarily disable the cache from DevTools during development.
Capitalization is another common source of trouble. Linux may treat Logo.png and logo.png as different files. The name in filename must exactly match the file on disk.
For practice, add a class to <h1>, change the button color in CSS, and display another image from static/images. Then move one file on purpose and watch its 404 request in the Network tab. This makes the connection between template paths and asset locations much easier to see.
The static folder is for public files that browsers may request. Never place secrets, configuration files, database dumps, or private uploads there because anyone who knows the URL can attempt to access them.
Related Articles
Keep reading within the same topic.
Flask Authentication: Login, Sessions, and Logout
Build Flask authentication with password hashes, login sessions, a current user, protected routes, and logout without storing plaintext passwords.
Flask CRUD with SQLAlchemy: Create, Read, Update, Delete
Build Flask and SQLAlchemy CRUD to create, read, update, and delete notes with validation, forms, and redirects.
Flask SQLAlchemy: Database Connection and Models
Connect Flask to SQLite with Flask-SQLAlchemy, create models and tables, then understand add, commit, and querying data.
Flask Project Structure: Blueprints, Templates, and Config
Organize a Flask project with an application factory, Blueprints, route modules, templates, static files, and separate configuration.