- What is TestInvite?
- Build Your First Test
- Run Your First Assessment
- Taking the Assessment
- Viewing the Results
- Question Bank Overview
- Common Question Features
- Scoring
- Question Types
- Question Authoring
- Browsing Questions
- Content Blocks
- Media Library
- Metadata Rules & Schema
- Tests Overview
- My Tests
- Creating a Test
- The Test Editor
- Test Settings
- Sections & Pages
- Adding Questions
- Page Builders
- Test Profile
- Reporting
- Test Papers
- Analytics
- Publishing a Test
- Test Library
- Marketplace
- Tasks Overview
- Creating a Task
- Task Dashboard
- Steps
- Task Settings
- Candidates
- Test Sessions
- Sent Mails
- Proctoring
- Analytics
- Analytics Overview
- Test Analytics
- Question Analytics
- Per-Question Analytics
- Results Explorer
- Segment Analysis
How Custom Reports Work
Each session's data is passed to your JavaScript function, which returns a plain object; your Nunjucks template renders that object to HTML. On servers the function runs in an isolated sandbox limited to 5 seconds and 128 MB.
Every custom report is produced by the same four-step pipeline, run separately for each session:
Session data ──► Function (optional) ──► output object ──► Template ──► HTML (Report Input) JavaScript plain JSON Nunjucks
- Report Input. TestInvite assembles one JSON object for the session: candidate, dates, task and step, the full scored result, the benchmark (norm) comparison, and the test's reporting settings. Every field is listed in the Report Input Reference.
- Function. Your JavaScript receives that object as its only argument and returns a plain object. This is where calculations belong: percentages, bands, rankings, text lookups, sorting and filtering. See Writing the Function.
- Template. The template is rendered with the function's return value as its variables — every key of the returned object becomes a variable. If the handler has no function, the template receives the Report Input itself. See Writing the Template.
- Display. The resulting HTML is shown inside an isolated container: the app's styles don't leak into your report and your styles don't leak into the app.
Where your code runs
The same handler runs in two places, and it must behave identically in both:
| Place | Used for | Environment |
|---|---|---|
| TestInvite servers | PDF export, REST API, result postbacks | An isolated sandbox: no network, no require/import, no file system, no environment variables. Limits: 5 seconds and 128 MB per run. console.log is allowed but its output is discarded. |
| The viewer's browser | Report view, handler editor preview | Runs locally in the page. |
Write the function as a pure calculation over its input: no network calls, no timers, no reliance on anything but data. A function that follows this rule produces the same report everywhere.
Why separate the function from the template
- Testability — the handler editor shows the function's output as JSON (Output: Data), so you can check every number before any HTML is involved.
- Readable templates — the template only prints and loops over names you chose (
overall.percent,dimensions[].band.label) instead of digging through raw result paths. - Reuse — the function's output is also returned by the REST API, so integrations get your computed values, not just HTML.
Reports are rendered when they are viewed. Editing a handler changes the report of every past and future session at once — there is nothing to recalculate, and nothing to roll back except the handler itself.