What the Moodle Marketplace review actually checks
I have put three plugins through it in the last month: one published, one sent back with a security blocker and since passed, one still in the queue. Here are the real timelines and the real findings, because almost nothing is written down about this and I wanted it to exist before I needed it.
8 October 2026
The timelines
| Plugin | Submitted | Reviewer opened it | Verdict | Waited |
|---|---|---|---|---|
| mod_onelife | 6 Sep | 16 Sep, 22:03 | 22:05, approved | 10 days |
| report_transcript | 14 Sep | 2 Oct, 21:46 | 22:05, changes needed | 18 days |
| report_transcript 1.0.1 | 3 Oct | 8 Oct, 21:12 | 21:13, passed | 5 days |
| theme_atrium | 1 Oct | still queued | — | 10 days and counting |
Look at the third and fourth columns. The reviewer opened One Life at 22:03 and approved it at 22:05. They opened Transcript at 21:46 and returned two findings at 22:05, nineteen minutes later. The resubmission is starker still: opened at 21:12 and passed at 21:13. One minute.
That is the single most useful thing I learned. The wait is the queue, not the review. Nobody is labouring over your code for three weeks. It sits untouched, then somebody picks it up and reaches a decision inside half an hour. Moodle quotes two to four weeks and that is honest, but it is nearly all queue.
The practical consequence: there is no partial credit for being nearly ready. A reviewer with twenty minutes will find the thing that is obviously wrong and send it back, and you go to the end of the queue. Getting it right before you submit is worth weeks, not hours.
What the automated checks catch
Before a human sees it, the submission runs roughly what moodle-plugin-ci runs: phplint, the Moodle coding standard, mess detector, copy-paste detector, PHPDoc checker, Mustache lint, a Grunt build check, upgrade savepoints, PHPUnit and Behat.
If you already run that matrix in GitHub Actions, this stage is free. Two things still caught me out on my own CI, both in the Moodle coding standard, and both the kind of thing you would never think to look for:
- Inline comments must start with a capital letter. I wrote a comment beginning with a function name,
user_allowed_editing(), and the build failed. It ismoodle.Commenting.InlineComment.NotCapital, and it fails the whole run because the check is set to zero warnings. - No
!importantin stylesheets. Reasonable, until you write a print stylesheet, where beating Bootstrap's own!importantutilities is the entire job. The answer is a scopedstylelint-disablewith the reason written down, not a workaround.
Neither is in any checklist I could find. Both cost me a red build.
What a human catches that no linter can
Transcript passed every automated check and still came back with an approval blocker. This is the finding, and it is the reason I am writing this post.
The plugin produces an academic transcript: a learner's completed courses with dates and final grades, as a page, as a PDF, and as a public verification page where anyone holding a code can confirm what was issued. It was careful about hidden grade items, and it mirrored core's logic for totals that aggregate something hidden.
What it never asked was whether the course showed grades to learners at all.
Moodle has a course setting, showgrades, that switches the gradebook off for students. Teachers use it before results are released. The plugin ignored it. So a total a teacher had deliberately withheld was printed on the page, embedded in the PDF, and made verifiable by anyone with the code on a page that needs no login.
No linter finds that. No test I had written found it, because every test I wrote assumed the thing I had already assumed. It takes somebody who knows the platform asking a question I had not thought to ask: not "is this grade hidden" but "is this learner allowed to see grades here".
The fix was small once named: apply both halves of the check core makes in its own overview report, the course setting and the capability, and ask them of the learner rather than of whoever is reading the document. Three tests now cover it. The README had claimed the transcript never reveals more than the learner's own gradebook; the claim is true now and it was not before.
The shape of what they look for
From two reviews and the published guidelines, the findings cluster into three kinds:
- Mechanical. A missing language string, a capability not declared, a privacy provider that does not match the schema. The automated checks get most of these; my other finding was a cache definition declared in
db/caches.phpwith no matchingcachedef_string. - Platform convention. Doing a thing your own way when core already has a way. The reviewer pointed at
grade_report_overview::setup_courses_data()and said: core already answers this question, match it. - Data visibility. Showing a user something they could not otherwise see. This is where the blockers are, and it is worth auditing your own plugin against it before you submit, because it is the category a linter cannot reach.
What I would do differently
Run the full moodle-plugin-ci matrix locally before submitting, not just in CI, so the feedback loop is minutes. Install moodlehq/moodle-cs and run the coding standard against a copy with LF line endings, because on Windows every file reports a line-ending error and drowns the real findings.
Then, before submitting, write down every place the plugin shows a user some data, and for each one ask what setting or capability controls whether they should see it. That one question would have caught my blocker. It took a stranger nineteen minutes to find what I had missed over weeks, because they asked it and I had not.
Timelines are from the Jira notification emails on MMR-208, MMR-244 and MMR-347. The reviewer on Transcript was right, the fix shipped the same day as v1.0.1, and v1.0.1 passed review on 8 October.