Skip to main content
Blueprint plugins allow you to add custom HTTP endpoints to Ayase Quart using Quart Blueprints. This enables you to extend the application with custom functionality, APIs, or integrations.

How blueprint plugins work

Blueprint plugins are standard Quart Blueprints that are automatically discovered and registered with the application at startup. They can:
  • Define custom routes and endpoints
  • Serve static files from custom locations
  • Implement custom business logic
  • Integrate with external services

Creating a blueprint plugin

1

Create your plugin file

Create a new Python file in src/ayase_quart/plugins/blueprints/:
touch src/ayase_quart/plugins/blueprints/my_blueprint.py
2

Define a Blueprint named 'bp'

Your module must export a Blueprint instance named bp:
from quart import Blueprint

# The module must have a Blueprint named 'bp'
bp = Blueprint(
    'my_blueprint',
    __name__,
    static_folder='../static',
    static_url_path='/static/plugins'
)

@bp.get('/my-custom-endpoint')
async def my_custom_endpoint():
    return {'message': 'Hello from my plugin!'}, 200

@bp.post('/my-api')
async def my_api():
    return {'status': 'success'}, 200
3

Enable plugins in config

Make sure plugins are enabled in your config.toml:
[search_plugins]
enabled = true
4

Restart Ayase Quart

Restart the application to register your blueprint. You should see:
Loading bp plugin: ayase_quart.plugins.blueprints.my_blueprint
5

Access your endpoint

Your custom endpoint is now available:
curl http://localhost:9001/my-custom-endpoint

Blueprint configuration

Blueprint constructor parameters

name
str
required
The name of your blueprint. Should be unique across all blueprints.
__name__
str
required
The import name of the blueprint. Always use __name__ to reference the current module.
static_folder
str
Path to static files directory, relative to the blueprint module. Use '../static' to share the main static folder.
static_url_path
str
URL path for serving static files. Cannot be /static (reserved).Example: /static/plugins

Complete example

Here’s the complete example from src/ayase_quart/plugins/blueprints/bp_example.py:
from quart import Blueprint

# This module must have a Blueprint named bp
# static_folder is relative to this module
# static_url_path cannot be '/static'
bp = Blueprint(
    'bp_example',
    __name__,
    static_folder='../static',
    static_url_path='/static/plugins',
)


# You are responsible for any endpoint collisions
@bp.get('/health_plugin_blueprint')
async def health_plugin_blueprint():
    return '<link rel="stylesheet" href="/static/plugins/example.css"> ok', 200
The example is wrapped in if 0: to maintain syntax highlighting without loading it by default. Remove this condition in your actual plugin.

Static files

Blueprint plugins can serve static files like CSS, JavaScript, and images.

Serving static files

bp = Blueprint(
    'my_plugin',
    __name__,
    static_folder='static',  # Relative to plugin file
    static_url_path='/static/my-plugin'
)
With this configuration:
  • Files in static/ (relative to your plugin) are served at /static/my-plugin/*
  • Example: static/style.csshttp://localhost:9001/static/my-plugin/style.css

Using static files in responses

@bp.get('/custom-page')
async def custom_page():
    html = '''
    <html>
        <head>
            <link rel="stylesheet" href="/static/my-plugin/style.css">
        </head>
        <body>
            <h1>Custom Page</h1>
            <script src="/static/my-plugin/script.js"></script>
        </body>
    </html>
    '''
    return html, 200

Route decorators

Blueprints support all standard Quart route decorators:

GET requests

@bp.get('/read-data')
async def read_data():
    return {'data': 'value'}, 200

POST requests

from quart import request

@bp.post('/submit-data')
async def submit_data():
    data = await request.get_json()
    return {'received': data}, 200

Multiple HTTP methods

@bp.route('/resource', methods=['GET', 'POST', 'PUT', 'DELETE'])
async def handle_resource():
    if request.method == 'GET':
        return {'action': 'read'}
    elif request.method == 'POST':
        return {'action': 'create'}
    # ... handle other methods

URL parameters

@bp.get('/posts/<int:post_id>')
async def get_post(post_id: int):
    return {'post_id': post_id}, 200

@bp.get('/boards/<board_name>/thread/<int:thread_num>')
async def get_thread(board_name: str, thread_num: int):
    return {
        'board': board_name,
        'thread': thread_num
    }, 200

Important considerations

You are responsible for any endpoint collisions. Ensure your routes don’t conflict with existing Ayase Quart endpoints.

Static URL paths

  • static_url_path cannot be /static - this is reserved for the main application
  • Use a unique path like /static/plugins or /static/my-plugin-name

Blueprint registration order

Blueprint plugins are registered after the main application blueprints. This means:
  • Main application routes take precedence
  • Your plugin routes cannot override core functionality
  • Avoid using route paths that might conflict with core routes

Async/await

All route handlers should be async functions since Quart is an async framework:
# Good
@bp.get('/data')
async def get_data():
    result = await some_async_operation()
    return result

# Avoid (synchronous)
@bp.get('/data')
def get_data():
    return {'data': 'value'}

Plugin loading process

The system automatically discovers blueprint plugins using this logic from i_blueprints.py:9:
def register_blueprint_plugins(app: Quart) -> None:
    package_module = importlib.import_module(f'{REPO_PKG}.plugins.blueprints')

    for _, module_name, is_pkg in pkgutil.iter_modules(
        package_module.__path__,
        package_module.__name__ + '.'
    ):
        module = importlib.import_module(module_name)

        if hasattr(module, 'bp'):
            print(f'Loading bp plugin: {module_name}')
            app.register_blueprint(module.bp)
The plugin loader:
  1. Scans all modules in the plugins.blueprints package
  2. Checks if each module has a bp attribute
  3. Registers the blueprint with the application
  4. Logs successful loading to stdout

Advanced usage

Accessing the database

You can import and use the database connections from Ayase Quart:
from quart import Blueprint
from ...db import db_q

bp = Blueprint('db_plugin', __name__)

@bp.get('/custom-query')
async def custom_query():
    async with db_q.pool.acquire() as conn:
        result = await conn.fetch('SELECT * FROM posts LIMIT 10')
        return {'posts': [dict(r) for r in result]}, 200

Using templates

You can use Ayase Quart’s template system:
from quart import Blueprint
from ...render import render_controller
from ...templates import env

bp = Blueprint('template_plugin', __name__)

@bp.get('/custom-page')
async def custom_page():
    template = env.from_string('<h1>{{title}}</h1>')
    return await render_controller(
        template,
        title='Custom Page'
    ), 200

Error handling

from quart import Blueprint
from werkzeug.exceptions import NotFound

bp = Blueprint('error_plugin', __name__)

@bp.get('/maybe-exists/<int:item_id>')
async def get_item(item_id: int):
    item = await fetch_item(item_id)
    if not item:
        raise NotFound(f'Item {item_id} not found')
    return {'item': item}, 200

@bp.errorhandler(404)
async def handle_not_found(error):
    return {'error': 'Resource not found'}, 404

Next steps

Plugin overview

Learn about the plugin system

Search plugins

Create custom search functionality

Build docs developers (and LLMs) love