Version 25.12 (LTS)

Table of Contents

Introduction#

Version 25.12 is the Long Term Support (LTS) release for the version 25 release cycle. As an LTS release, it will receive security updates and critical bug fixes for an extended period. If you run into any issues, please raise a concern on GitHub.

What to know#

More details in the Changelog. Notable new or breaking features, and what to upgrade:

Python 3.9 removed, Python 3.14 added#

Sanic now requires Python 3.10 or newer. Python 3.9 has been dropped, and Python 3.14 support has been added.

Daemon mode#

You can now run Sanic as a background daemon process directly from the CLI.

sanic path.to.app -D
sanic path.to.app --daemon

This also introduces convenience commands for managing daemon processes:

sanic path.to.app status    # Check if running
sanic path.to.app stop      # Stop the daemon

Additional options are available:

--pidfile PATH    # Custom PID file location
--logfile PATH    # Log output to file
--user USER       # Run as specified user
--group GROUP     # Run as specified group

Lower-level commands are also available:

sanic kill --pid=<PID>
sanic kill --pidfile=<PATH>
sanic status --pid=<PID>
sanic status --pidfile=<PATH>

Sanic now provides granular control over symlinks in static file serving with two new parameters:

Parameter Default Description
follow_external_symlink_files False Allow serving file symlinks that point outside the static root
follow_external_symlink_dirs False Allow serving files from directory symlinks that point outside the static root

Examples

Secure defaults (block external symlinks):

# Symlinks pointing outside /var/www/static will return 404
app.static("/static", "/var/www/static")

Allow file symlinks only:

# Serves /var/www/static/config.json -> /etc/app/config.json
app.static("/static", "/var/www/static", follow_external_symlink_files=True)

Allow directory symlinks only:

# Serves files from /var/www/static/images/ -> /shared/images/
app.static("/static", "/var/www/static", follow_external_symlink_dirs=True)

Custom configuration converters#

You can now extend how Sanic parses environment variables into configuration values. By default, Sanic converts environment variables using str, str_to_bool, float, and int converters (tried in reverse order). You can add your own converters to handle custom types.

Simple converter - a callable that takes a string and returns a converted value:

class UltimateAnswer:
    def __init__(self, answer):
        self.answer = int(answer)

config = Config(converters=[UltimateAnswer])
app = Sanic("MyApp", config=config)

# Or register after creation
app.config.register_type(UltimateAnswer)

DetailedConverter - for converters that need more context (full key name, config key, value, and defaults):

from sanic.config import Config, DetailedConverter

class TypeAwareConverter(DetailedConverter):
    def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):
        if config_key in defaults:
            # Cast to the same type as the default value
            return type(defaults[config_key])(value)
        raise ValueError  # Fall through to next converter

config = Config(
    defaults={"PORT": 8000, "DEBUG": False},
    converters=[TypeAwareConverter()]
)

Automatic charset for text content types#

Text content types (text/*) now automatically include charset=UTF-8 when serving static files and file responses.

Task creation returns the task#

When creating a task via app.add_task(), the asyncio Task object is now returned, allowing you to await or check its result later.

task = app.add_task(some_coroutine())
# Later...
result = await task

Improved CLI error messages#

Tracerite has been upgraded to v2.2.0, providing better formatted exception tracebacks in the CLI with improved chained exception display.

Thank you#

Thank you to everyone that participated in this release: :clap:

@ahopkins @amarquard089 @ChihweiLHBird @dhensen @dungarpan @gazpachoking @helioascorreia @jameslovespancakes @Peopl3s @tdaron @tiejunhu @tkosman @Tronic @wojonet


If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: financial contributions.