How to Fix ImportError: cannot import name 'escape' from 'jinja2' in Flask 2.3
If you’ve upgraded to Flask 2.3 and run into ImportError: cannot import name ‘escape’ from ‘jinja2’, let’s figure out what’s going on.
Flask 2.3 requires Jinja2 >=3.1. Jinja2 removed escape from its API — it’s now in MarkupSafe — to encourage using template filters over direct imports.
This affects custom helpers or extensions that imported escape directly. Why update? It aligns with Jinja’s design for safer, more maintainable templating.
Why This Error Occurs
The Jinja2 3.1 changelog states: “Markup and escape should be imported from MarkupSafe.”
You might wonder why — it separates escaping logic (MarkupSafe) from templating (Jinja2), promoting better modularity.
Common in:
- Custom template helpers.
- Extensions like Flask-Admin, Flask-Bootstrap.
Full error:
ImportError: cannot import name 'escape' from 'jinja2' (/path/to/jinja2/__init__.py)
| Cause | Flask Version | Jinja2 Version |
|---|---|---|
Direct from jinja2 import escape | >=2.3 | >=3.1 |
Reproducing the Error
# app.py
from flask import Flask
from jinja2 import escape # <- Breaks here!
app = Flask(__name__)
@app.route('/')
def index():
return escape('<script>alert(1)</script>') # Example usage
if __name__ == '__main__':
app.run(debug=True)
Run these commands to reproduce:
$ pip install flask==2.3.0
$ python app.py
You’ll see the ImportError.
Of course, reproducing helps us understand the issue before fixing it.
Solution 1: Update the Import
We’ll update the import:
# OLD (broken)
from jinja2 import escape
# NEW (fixed)
from markupsafe import escape
Full fixed app.py:
from flask import Flask
from markupsafe import escape # Fixed import
app = Flask(__name__)
@app.route('/')
def index():
safe_html = escape('<script>alert(1)</script>')
return f'<h1>Safe: {safe_html}</h1>'
if __name__ == '__main__':
app.run(debug=True)
Test: python app.py — the app starts without error.
Solution 2: Downgrade Jinja2 (Temporary)
Pin older Jinja2:
pip install "jinja2<3.1"
| Pros | Cons |
|---|---|
| Quick fix, no code changes | Security risks from older Jinja2; blocks Flask/Jinja2 updates; not sustainable long-term |
We generally avoid this in production — it trades short-term convenience for long-term maintenance issues.
Update Extensions
Scan code:
grep -r "from jinja2 import escape" src/
Common fixes:
- Flask-Admin: Update to >=1.6.1.
- Flask-Bootstrap: Update to >=3.4.1.
Verify Fix
pip list | grep -E 'flask|jinja2|markupsafe'- Run tests/app.
- Check templates use
|efilter:{{ user_input | e }}.
Best Practices for Flask + Jinja2
- Avoid direct imports: Use Jinja
|e(escape) filter. - Jinja2 filters:
{{ user_input | e }} # HTML escape {{ user_input | safe }} # Bypass (use sparingly!) - requirements.txt:
Flask==2.3.0 Jinja2>=3.1 MarkupSafe>=2.1 - Pre-commit hook for imports:
# .pre-commit-config.yaml repos: - repo: local hooks: - id: check-jinja-imports entry: grep -r "from jinja2 import escape" - CI/CD: Add
pip-audit(see post 23).
Conclusion
Update to from markupsafe import escape for Flask 2.3 + Jinja2 3.1 compatibility. Prefer template filters for escaping.
Fixed your app? Share your setup! See related: Flask dependency audits.
Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming