Skip to main content

Extensions

Flask extensions provide additional functionality to Flask applications. This guide covers popular extensions and advanced Flask features.

Flask-SQLAlchemy (Database ORM)

pip install Flask-SQLAlchemy
from flask_sqlalchemy import SQLAlchemy

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)

Flask-Migrate (Database Migrations)

pip install Flask-Migrate
from flask_migrate import Migrate

migrate = Migrate(app, db)

# Commands
# flask db init
# flask db migrate -m "Initial migration"
# flask db upgrade

Flask-Login (User Session Management)

pip install Flask-Login
from flask_login import LoginManager, UserMixin, login_user, logout_user

login_manager = LoginManager(app)
login_manager.login_view = 'login'

class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)

@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

Flask-WTF (Forms)

pip install Flask-WTF
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email

class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Sign In')

Flask-Mail (Email)

pip install Flask-Mail
from flask_mail import Mail, Message

app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'your-email@gmail.com'
app.config['MAIL_PASSWORD'] = 'your-password'

mail = Mail(app)

@app.route('/send-email')
def send_email():
msg = Message('Hello',
sender='your-email@gmail.com',
recipients=['recipient@example.com'])
msg.body = "This is a test email"
msg.html = "<h1>This is a test email</h1>"
mail.send(msg)
return "Email sent!"

Advanced Extensions

Flask-Caching (Caching)

pip install Flask-Caching
from flask_caching import Cache

app.config['CACHE_TYPE'] = 'redis'
app.config['CACHE_REDIS_URL'] = 'redis://localhost:6379/0'

cache = Cache(app)

@app.route('/expensive')
@cache.cached(timeout=300) # Cache for 5 minutes
def expensive_operation():
# Expensive computation
return result

# Cache with dynamic key
@app.route('/user/<int:user_id>')
@cache.cached(timeout=300, key_prefix='user_profile')
def user_profile(user_id):
return f"Profile for user {user_id}"

# Manual caching
def get_user_count():
count = cache.get('user_count')
if count is None:
count = User.query.count()
cache.set('user_count', count, timeout=600)
return count

Flask-Limiter (Rate Limiting)

pip install Flask-Limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)

@app.route('/api/data')
@limiter.limit("5 per minute")
def get_data():
return {"data": "sensitive information"}

@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute")
def login():
# Login logic with rate limiting
pass

Flask-CORS (Cross-Origin Resource Sharing)

pip install Flask-CORS
from flask_cors import CORS, cross_origin

# Enable CORS for all routes
CORS(app)

# Or configure specifically
CORS(app, resources={
r"/api/*": {
"origins": ["http://localhost:3000", "https://your-frontend.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"]
}
})

# Per-route CORS
@app.route('/api/data')
@cross_origin(origins=['http://localhost:3000'])
def api_data():
return {"data": "value"}

Flask-JWT-Extended (JWT Authentication)

pip install Flask-JWT-Extended
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity

app.config['JWT_SECRET_KEY'] = 'super-secret'
jwt = JWTManager(app)

@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')

# Verify credentials
if username == 'admin' and password == 'password':
access_token = create_access_token(identity=username)
return {'access_token': access_token}
return {'message': 'Invalid credentials'}, 401

@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
current_user = get_jwt_identity()
return {'logged_in_as': current_user}

Flask-Admin (Admin Interface)

pip install Flask-Admin
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView

admin = Admin(app, name='My App Admin', template_mode='bootstrap3')

# Add model views
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))

# Custom model view
class UserView(ModelView):
column_exclude_list = ['password_hash']
column_searchable_list = ['username', 'email']
column_filters = ['username', 'email']
form_excluded_columns = ['password_hash']

def is_accessible(self):
return current_user.is_authenticated and current_user.is_admin

admin.add_view(UserView(User, db.session))

Flask-Uploads (File Uploads)

pip install Flask-Uploads
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class

photos = UploadSet('photos', IMAGES)

app.config['UPLOADED_PHOTOS_DEST'] = 'static/uploads'
configure_uploads(app, photos)
patch_request_class(app) # 16MB max file size

@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST' and 'photo' in request.files:
filename = photos.save(request.files['photo'])
return f"Photo saved as {filename}"
return render_template('upload.html')

Flask-Marshmallow (Serialization)

pip install flask-marshmallow marshmallow-sqlalchemy
from flask_marshmallow import Marshmallow

ma = Marshmallow(app)

class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
load_instance = True
exclude = ('password_hash',)

user_schema = UserSchema()
users_schema = UserSchema(many=True)

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
user = User.query.get_or_404(user_id)
return user_schema.dump(user)

@app.route('/api/users')
def get_users():
users = User.query.all()
return users_schema.dump(users)

Task Queue Extensions

Flask-RQ (Redis Queue)

pip install Flask-RQ2
from flask_rq2 import RQ

rq = RQ(app)

@rq.job
def send_email(email, subject, body):
# Email sending logic
time.sleep(10) # Simulate slow operation
return f"Email sent to {email}"

@app.route('/send-async-email')
def send_async_email():
job = send_email.queue('user@example.com', 'Hello', 'Test message')
return f"Job {job.id} queued"

Celery Integration

pip install celery redis
# celery_app.py
from celery import Celery

def make_celery(app):
celery = Celery(
app.import_name,
backend=app.config['CELERY_RESULT_BACKEND'],
broker=app.config['CELERY_BROKER_URL']
)

class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)

celery.Task = ContextTask
return celery

# app.py
app.config.update(
CELERY_BROKER_URL='redis://localhost:6379/0',
CELERY_RESULT_BACKEND='redis://localhost:6379/0'
)

celery = make_celery(app)

@celery.task
def send_async_email(email, subject, body):
# Email sending logic
return f"Email sent to {email}"

@app.route('/send-email')
def send_email_route():
send_async_email.delay('user@example.com', 'Hello', 'Test')
return "Email queued for sending"

API Extensions

Flask-RESTful (REST API)

pip install Flask-RESTful
from flask_restful import Api, Resource, reqparse

api = Api(app)

class UserListAPI(Resource):
def get(self):
users = User.query.all()
return [{'id': u.id, 'username': u.username} for u in users]

def post(self):
parser = reqparse.RequestParser()
parser.add_argument('username', required=True)
parser.add_argument('email', required=True)
args = parser.parse_args()

user = User(username=args['username'], email=args['email'])
db.session.add(user)
db.session.commit()

return {'id': user.id, 'username': user.username}, 201

class UserAPI(Resource):
def get(self, user_id):
user = User.query.get_or_404(user_id)
return {'id': user.id, 'username': user.username}

def put(self, user_id):
user = User.query.get_or_404(user_id)
parser = reqparse.RequestParser()
parser.add_argument('username')
args = parser.parse_args()

if args['username']:
user.username = args['username']
db.session.commit()

return {'id': user.id, 'username': user.username}

def delete(self, user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return '', 204

api.add_resource(UserListAPI, '/api/users')
api.add_resource(UserAPI, '/api/users/<int:user_id>')

Flask-RESTX (Swagger Documentation)

pip install flask-restx
from flask_restx import Api, Resource, fields

api = Api(app, doc='/docs/', version='1.0', title='My API',
description='A simple API with Flask-RESTX')

user_model = api.model('User', {
'id': fields.Integer(readonly=True, description='User ID'),
'username': fields.String(required=True, description='Username'),
'email': fields.String(required=True, description='Email address')
})

@api.route('/users')
class UserList(Resource):
@api.doc('list_users')
@api.marshal_list_with(user_model)
def get(self):
"""Fetch all users"""
return User.query.all()

@api.doc('create_user')
@api.expect(user_model)
@api.marshal_with(user_model, code=201)
def post(self):
"""Create a new user"""
data = api.payload
user = User(username=data['username'], email=data['email'])
db.session.add(user)
db.session.commit()
return user, 201

Security Extensions

Flask-Talisman (Security Headers)

pip install flask-talisman
from flask_talisman import Talisman

Talisman(app, force_https=True)

# Or with custom CSP
csp = {
'default-src': "'self'",
'script-src': "'self' 'unsafe-inline'",
'style-src': "'self' 'unsafe-inline'"
}

Talisman(app, content_security_policy=csp)

Flask-Bcrypt (Password Hashing)

pip install Flask-Bcrypt
from flask_bcrypt import Bcrypt

bcrypt = Bcrypt(app)

class User(db.Model):
password_hash = db.Column(db.String(60), nullable=False)

def set_password(self, password):
self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')

def check_password(self, password):
return bcrypt.check_password_hash(self.password_hash, password)

Monitoring Extensions

Flask-APM (Application Performance Monitoring)

pip install elastic-apm
from elasticapm.contrib.flask import ElasticAPM

app.config['ELASTIC_APM'] = {
'SERVICE_NAME': 'my-flask-app',
'SECRET_TOKEN': 'your-secret-token',
'SERVER_URL': 'https://your-apm-server.com:8200',
}

apm = ElasticAPM(app)

Flask-Prometheus (Metrics)

pip install prometheus-flask-exporter
from prometheus_flask_exporter import PrometheusMetrics

metrics = PrometheusMetrics(app)

# Custom metrics
REQUEST_COUNT = metrics.counter(
'requests_total', 'Total requests', ['method', 'endpoint']
)

@app.before_request
def before_request():
REQUEST_COUNT.labels(method=request.method, endpoint=request.endpoint).inc()

Testing Extensions

Flask-Testing

pip install Flask-Testing
from flask_testing import TestCase

class MyTest(TestCase):
def create_app(self):
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
return app

def setUp(self):
db.create_all()

def tearDown(self):
db.session.remove()
db.drop_all()

def test_user_creation(self):
user = User(username='test', email='test@example.com')
db.session.add(user)
db.session.commit()

self.assertEqual(User.query.count(), 1)

Custom Extensions

Creating a Custom Extension

# my_extension.py
class MyExtension:
def __init__(self, app=None):
self.app = app
if app is not None:
self.init_app(app)

def init_app(self, app):
app.config.setdefault('MY_EXTENSION_SETTING', 'default_value')
app.teardown_appcontext(self.teardown)

# Add extension to app
if not hasattr(app, 'extensions'):
app.extensions = {}
app.extensions['my_extension'] = self

def teardown(self, exception):
# Cleanup code
pass

def my_method(self):
return "Extension method called"

# Usage
my_ext = MyExtension(app)

@app.route('/test-extension')
def test_extension():
return my_ext.my_method()

Extension with Configuration

class ConfigurableExtension:
def __init__(self, app=None, **kwargs):
self.config = kwargs
if app:
self.init_app(app)

def init_app(self, app):
# Merge config
config = app.config.get('MY_EXTENSION', {})
config.update(self.config)
self.config = config

# Register with app
app.extensions['configurable_extension'] = self

def process_data(self, data):
timeout = self.config.get('timeout', 30)
return f"Processed with timeout: {timeout}"

# Usage
ext = ConfigurableExtension(app, timeout=60, debug=True)

Extension Best Practices

Extension Organization

# 1. Use application factory pattern
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])

# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
mail.init_app(app)
cache.init_app(app)

return app

# 2. Group related extensions
class Extensions:
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
mail = Mail()
cache = Cache()

extensions = Extensions()

def init_extensions(app):
extensions.db.init_app(app)
extensions.migrate.init_app(app, extensions.db)
extensions.login_manager.init_app(app)
extensions.mail.init_app(app)
extensions.cache.init_app(app)

# 3. Lazy loading
class LazyExtension:
def __init__(self):
self._extension = None

def init_app(self, app):
self._extension = SomeExtension(app)

def __getattr__(self, name):
if self._extension is None:
raise RuntimeError('Extension not initialized')
return getattr(self._extension, name)

# 4. Environment-specific configuration
def configure_extensions(app):
if app.config['TESTING']:
# Test-specific extension config
app.config['CACHE_TYPE'] = 'simple'
elif app.config['DEBUG']:
# Development config
app.config['CACHE_TYPE'] = 'simple'
else:
# Production config
app.config['CACHE_TYPE'] = 'redis'
app.config['CACHE_REDIS_URL'] = os.environ.get('REDIS_URL')

# 5. Extension error handling
try:
from flask_some_extension import SomeExtension
some_ext = SomeExtension(app)
except ImportError:
some_ext = None
app.logger.warning('SomeExtension not available')

@app.route('/feature')
def feature():
if some_ext is None:
return "Feature not available", 503
return some_ext.do_something()

Configuration Management

# config.py
class Config:
# Flask-Mail
MAIL_SERVER = os.environ.get('MAIL_SERVER', 'localhost')
MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']

# Flask-Caching
CACHE_TYPE = os.environ.get('CACHE_TYPE', 'simple')
CACHE_REDIS_URL = os.environ.get('CACHE_REDIS_URL', 'redis://localhost:6379/0')

# Flask-Limiter
RATELIMIT_STORAGE_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/1')

# JWT
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY', 'super-secret')
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)

@staticmethod
def init_app(app):
pass

class DevelopmentConfig(Config):
DEBUG = True
CACHE_TYPE = 'simple'

class ProductionConfig(Config):
CACHE_TYPE = 'redis'

@classmethod
def init_app(cls, app):
Config.init_app(app)

# Log to syslog
import logging
from logging.handlers import SysLogHandler
syslog_handler = SysLogHandler()
syslog_handler.setLevel(logging.WARNING)
app.logger.addHandler(syslog_handler)