forgejo-form
A Forgejo plugin that renders interactive forms from YAML in issue comments. Designed for coding agents that need structured input from humans — plan approvals, multiple-choice questions, and one-click actions.
An agent posts a comment containing a ```forgejo-form code block with YAML. The plugin transforms it into an interactive HTML form. When a user submits the form, a response comment is posted summarizing their choices, and optional side effects (assignee changes, project column moves) are applied.
See TOUR.md for a detailed walkthrough with screenshots.
Quick Start
Docker Compose / bare metal
./install.sh /path/to/gitea/custom
# e.g. ./install.sh /data/gitea
docker compose restart
The install script copies JS/CSS to public/assets/ and injects <link>/<script> tags into templates/custom/header.tmpl and footer.tmpl. It appends to existing files safely using marker comments.
To remove:
./uninstall.sh /path/to/gitea/custom
Kubernetes
The container image packages the plugin files at /plugin/ in the correct directory layout. Use it as an init container that copies into Forgejo's data volume:
initContainers:- name:install-forgejo-formimage:codeberg.org/myers/forgejo-form:latestcommand:["cp","-r","/plugin/.","/data/gitea/"]volumeMounts:- mountPath:/dataname:dataUsage
Question Form
Post a comment with a ```forgejo-form code block:
type:questiontitle:Review implementation planquestions:- header:Auth methodquestion:Which authentication approach should we use?multiSelect:falseoptions:- label:JWT tokensdescription:Stateless, scalable- label:Session cookiesdescription:Simpler, built-in CSRF- header:Databasequestion:Which database should we use?multiSelect:falseoptions:- label:PostgreSQLdescription:Full-featured- label:SQLitedescription:Noseparate serveron_submit:assignee:coding-botcolumn:In Progress- 1-4 questions, each with 2-4 options
- Every question gets an implicit "Other" free-text option
multiSelect: truerenders checkboxes instead of radio buttonstitleis optional
Button Form
A shorthand for single-action approvals:
type:buttonlabel:Accept Plancomment:I accept this plan.on_submit:assignee:coding-botcolumn:To DoSide Effects
The on_submit block is optional. Both fields are independent:
| Field | Effect |
|---|---|
assignee |
Assigns the named user to the issue |
column |
Moves the issue to the named project board column |
Side effect failures are logged but don't block the response comment.
Response Format
When submitted, the plugin posts a comment like:
**Auth method**: JWT tokens — *Stateless, scalable*
**Database**: SQLite — *No separate server*
The comment includes a hidden ```forgejo-form-response marker that links back to the original form. This marker is how the plugin knows a form has been answered — if the response comment is deleted, the form becomes active again.
Development
pnpm install
pnpm run build # bundles src/forgejo-form.js with js-yaml via esbuild
To test locally against a Forgejo dev instance:
./install.sh /path/to/gitea/custom
# restart forgejo, then post comments with forgejo-form code blocks
How It Works
footer.tmplloadsforgejo-form.json issue pages (gated by{{if .Issue}})- On
DOMContentLoaded, the JS scans forcode.language-forgejo-formelements - YAML is parsed and validated, then the
<pre>block is replaced with an interactive form - The JS also scans for
code.language-forgejo-form-responsemarkers to build a set of already-answered form comment IDs - On submit: a response comment is POSTed to the existing comment endpoint, side effects fire via existing Forgejo endpoints, and the page reloads
No server-side changes. No API extensions. Everything runs through Forgejo's existing comment and issue endpoints using same-origin fetch with cookie auth.