Write your first plugin¶
A study plugin defines a whole study flow (not just a component). Start from the shipped
empty_template.
1. Copy the template¶
cp -r server/plugins/empty_template server/plugins/myplugin
A plugin folder is a Python package with an __init__.py and a templates/ directory.
2. Declare the plugin¶
In myplugin/__init__.py, set the metadata and a Flask Blueprint whose url_prefix is your plugin
name:
from flask import Blueprint, request, redirect, render_template
__plugin_name__ = "myplugin"
__version__ = "0.1.0"
__author__ = "You"
__description__ = "My custom study flow."
bp = Blueprint(__plugin_name__, __plugin_name__, url_prefix=f"/{__plugin_name__}")
3. Implement the four lifecycle endpoints¶
See Concepts → plugin lifecycle. Minimum viable set:
# (a) /create — render the parameter form. Uncomment to make the plugin appear in Administration.
@bp.route("/create")
def create():
return render_template("myplugin_create.html")
# (b) /initialize — runs after the study is created. Do heavy setup in a BACKGROUND process so the
# request returns immediately, then mark the study initialized & active.
from multiprocessing import Process
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from models import UserStudy
def long_initialization(guid):
session = Session(create_engine('sqlite:///instance/db.sqlite'))
study = session.query(UserStudy).filter(UserStudy.guid == guid).first()
# ... train models / prepare data ...
study.initialized = True
study.active = True
session.commit(); session.close()
@bp.route("/initialize")
def initialize():
guid = request.args.get("guid")
Process(target=long_initialization, daemon=True, args=(guid,)).start()
return redirect(request.args.get("continuation_url"))
# (c) /join — entry point when a participant opens the study URL. You drive the flow from here.
@bp.route("/join", methods=["GET"])
def join():
assert "guid" in request.args, "guid must be present"
return render_template("myplugin_join.html")
The /create → /create-user-study contract
Your /create form must ultimately POST to the framework's /create-user-study endpoint (the
template in empty_template shows how). That is what actually persists the UserStudy row and
triggers /initialize.
Optionally add /results to provide a custom evaluation view; if you omit it, EasyStudy falls back to
the default results page from utils.
4. Reuse the shared building blocks¶
Your /join flow can reuse utils endpoints instead of reinventing them:
- Preference elicitation, participant-details and final pages (GUIs).
- Interaction logging: call
utils' logging so your data lands in the standardInteractiontable (see Data model) — keeping your study analyzable with the same tools as everyone else.
5. Run it¶
Restart the server; your plugin appears in the Administration UI (once /create is enabled). Create an
instance, open the join URL, and verify the flow.
Tips¶
- Long/blocking work (training, downloads) must go in the background daemon, never inside the request.
- Set
study.initialization_errorif setup fails, so the admin UI can surface it. - Keep heavy dependencies optional (import lazily) so your plugin doesn't break the lightweight core.