diff --git a/openflexure_microscope/plugins/testing/dynamic_form_example/__init__.py b/openflexure_microscope/plugins/testing/dynamic_form_example/__init__.py
new file mode 100644
index 00000000..571c9f38
--- /dev/null
+++ b/openflexure_microscope/plugins/testing/dynamic_form_example/__init__.py
@@ -0,0 +1,2 @@
+from .plugin import DynamicExamplePlugin
+from . import api
diff --git a/openflexure_microscope/plugins/testing/dynamic_form_example/api.py b/openflexure_microscope/plugins/testing/dynamic_form_example/api.py
new file mode 100644
index 00000000..7a46ca94
--- /dev/null
+++ b/openflexure_microscope/plugins/testing/dynamic_form_example/api.py
@@ -0,0 +1,24 @@
+from openflexure_microscope.devel import (
+ MicroscopeViewPlugin,
+ JsonResponse,
+ request,
+ jsonify,
+ taskify,
+)
+
+import logging
+
+
+class DoAPI(MicroscopeViewPlugin):
+ """
+ A dynamic example API plugin
+ """
+
+ def get(self):
+ values = {"val_int": self.plugin.val_int}
+ return jsonify(values)
+
+ def post(self):
+ self.plugin.val_int += 1
+
+ return jsonify({"response": "completed"})
diff --git a/openflexure_microscope/plugins/testing/dynamic_form_example/plugin.py b/openflexure_microscope/plugins/testing/dynamic_form_example/plugin.py
new file mode 100644
index 00000000..b25afb84
--- /dev/null
+++ b/openflexure_microscope/plugins/testing/dynamic_form_example/plugin.py
@@ -0,0 +1,52 @@
+import random
+import time
+import os
+import json
+from openflexure_microscope.devel import MicroscopePlugin, update_task_progress
+
+from .api import DoAPI
+
+
+class DynamicExamplePlugin(MicroscopePlugin):
+ """
+ An example plugin using a comprehensive form
+ """
+
+ api_views = {"/do": DoAPI}
+
+ def __init__(self):
+ MicroscopePlugin.__init__(self)
+
+ self.val_int = 0
+ self.val_str = "Hello"
+
+ self.set_gui(self.dynamic_form)
+
+ def dynamic_form(self):
+ return {
+ "id": "test-plugin",
+ "icon": "pets",
+ "forms": [
+ {
+ "name": "Simple request",
+ "isCollapsible": False,
+ "isTask": False,
+ "selfUpdate": True,
+ "route": "/do",
+ "submitLabel": "Do things",
+ "schema": [
+ {
+ "fieldType": "numberInput",
+ "name": "val_int",
+ "label": "Number value",
+ "minValue": 0,
+ },
+ {
+ "fieldType": "htmlBlock",
+ "name": "html_block",
+ "content": f"Value is:
{self.val_int}.",
+ },
+ ],
+ }
+ ],
+ }