---
title: "Report card template for JavaScript — quario"
description: "A ready-made quario school report card for JavaScript. Subjects grouped by department, a score and a grade per subject, averages, attendance, a tutor comment and signatures. Copy the definition, point it at your grades, and render to PDF, Word, Excel, or HTML."
url: "https://getquario.com/templates/report-card"
---

# Report card template for JavaScript

A report card goes home with every student at the end of term, so a school prints hundreds in a
week. Each one has the same layout and different marks. The marks already sit in the school’s
records. What turns a percentage into a letter grade is a policy, and it has to be the same on every
card. This is a ready-made quario report card — copy it, point it at your grades, and render to PDF,
Word, Excel, or HTML.

## The template

The whole report card is a definition — data you can version and review. The school line, the
student block, the subjects by department, the averages, the tutor’s comment and the three
signatures are all in here. This is the exact file the preview below renders:

```json title="report-card.report.json"
{
  "data": "$.report.subjects[*]",
  "aggregates": { "average": "avg:=@.score", "subjects": "count" },
  "style": { "family": "sans", "size": 10 },
  "header": [
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "{{ $.input.report.school.name }}", "width": 60,
          "style": { "size": 10, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ $.input.report.school.street }}, {{ $.input.report.school.city }}",
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right" } }
      ]
    },
    {
      "type": "split",
      "style": { "spaceBefore": 28 },
      "slots": [
        { "type": "text", "value": "Report card", "width": 60,
          "style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": [{ "value": "{{ $.input.report.term }} · Issued " }, { "value": "{{ $.input.report.issued }}" }],
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 34 }, { "width": 22 }, { "width": 22 }, {}],
      "rows": [
        {
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97", "paddingTop": 26 },
          "slots": [
            { "type": "text", "value": "Student" },
            { "type": "text", "value": "Year" },
            { "type": "text", "value": "Tutor" },
            { "type": "text", "value": "Attendance" }
          ]
        },
        {
          "style": { "size": 10, "bold": false, "color": "#1e1f22", "paddingBottom": 26 },
          "slots": [
            { "type": "text", "value": "{{ $.input.report.student.name }} · {{ $.input.report.student.number }}", "style": { "bold": true } },
            { "type": "text", "value": "{{ $.input.report.student.year }}" },
            { "type": "text", "value": "{{ $.input.report.student.tutor }}" },
            { "type": "text", "value": "{{ $.input.report.attendance.present }} of {{ $.input.report.attendance.sessions }} days" }
          ]
        }
      ]
    }
  ],
  "groups": [
    {
      "name": "department",
      "by": "=@.department",
      "aggregates": { "average": "avg:=@.score" },
      "header": [
        {
          "type": "split",
          "style": { "spaceBefore": 12, "spaceAfter": 2 },
          "slots": [
            { "type": "text", "value": "{{ department.key }}", "width": 50,
              "style": { "size": 11, "bold": true, "color": "#1e1f22" } },
            { "type": "text", "value": [{ "value": "Average " }, { "value": "{{ department.average / 100 }}", "style": { "format": { "kind": "percent", "digits": 0 } } }],
              "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right" } }
          ]
        }
      ]
    }
  ],
  "detail": {
    "header": {
      "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
        "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
    },
    "row": { "style": { "color": "#5e6062" } },
    "columns": [
      { "header": "Subject", "value": "{{ @.subject }}", "width": 18, "style": { "color": "#1e1f22" } },
      { "header": "Teacher", "value": "{{ @.teacher }}", "width": 20 },
      { "header": { "value": "Score", "style": { "align": "right" } }, "value": "{{ @.score / 100 }}", "width": 10,
        "style": { "align": "right", "color": "#1e1f22", "format": { "kind": "percent", "digits": 0 } } },
      { "header": { "value": "Grade", "style": { "align": "center" } },
        "value": "{{ @.score >= 85 ? 'A' : @.score >= 70 ? 'B' : @.score >= 55 ? 'C' : @.score >= 40 ? 'D' : 'E' }}", "width": 10,
        "style": { "align": "center", "bold": true, "color": "#1e1f22" } },
      { "header": "Comment", "value": "{{ @.comment }}", "width": 42, "style": { "size": 9 } }
    ]
  },
  "footer": [
    {
      "type": "split",
      "style": { "spaceBefore": 14, "paddingTop": 6,
        "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
      "slots": [
        { "type": "text", "value": "{{ $.subjects }} subjects", "width": 38,
          "style": { "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": [{ "value": "Overall average " }, { "value": "{{ round($.average, 0) / 100 }}", "style": { "format": { "kind": "percent", "digits": 0 } } }],
          "style": { "bold": true, "color": "#1e1f22", "align": "right" } }
      ]
    },
    {
      "type": "text",
      "value": "Grades: A 85% and over · B 70–84% · C 55–69% · D 40–54% · E below 40%",
      "style": { "size": 8, "color": "#989a97", "spaceBefore": 4 }
    },
    {
      "type": "split",
      "style": { "spaceBefore": 26 },
      "slots": [
        { "type": "text", "value": "Tutor's comment", "width": 22,
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "{{ $.input.report.tutorComment }}", "style": { "color": "#1e1f22" } }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 30 }, { "width": 5 }, { "width": 30 }, { "width": 5 }, {}],
      "rows": [
        {
          "style": { "paddingTop": 64 },
          "slots": [
            { "type": "text", "value": "", "style": { "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" } },
            { "type": "text", "value": "" },
            { "type": "text", "value": "", "style": { "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" } },
            { "type": "text", "value": "" },
            { "type": "text", "value": "", "style": { "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" } }
          ]
        },
        {
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97", "paddingTop": 4 },
          "slots": [
            { "type": "text", "value": "Tutor" },
            { "type": "text", "value": "" },
            { "type": "text", "value": "Principal" },
            { "type": "text", "value": "" },
            { "type": "text", "value": "Parent or guardian" }
          ]
        }
      ]
    }
  ],
  "page": {
    "footer": [
      {
        "type": "text",
        "value": "Scores are the term's combined coursework and assessment marks. Questions about this report go to the student's tutor.",
        "style": { "size": 8, "color": "#989a97", "paddingTop": 10,
          "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" }
      }
    ]
  }
}
```

The grade bands live in the definition, not in your code. The _Grade_ column is one expression,
`@.score >= 85 ? 'A' : @.score >= 70 ? 'B' : …`, and the line under the table prints the same bands
in words. A school that moves the boundary for an A changes this file, and the change shows up in
review beside the wording that explains it.

The departments are a **group** on `=@.department`. Each one carries `department.average`, an `avg`
over its own subjects, which its header prints on the right. The overall average in the footer is a
report aggregate over every subject. `round($.average, 0)` makes sure the stored value is the whole
percentage the card prints.

The data stores scores as whole numbers out of 100, and the cells divide by 100 under the `percent` kind
with `digits: 0`. So `84` reads _84%_ on the card and is a real percentage in the workbook.

## The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. `$.report.subjects[*]` in the definition selects its rows, and `$.input` reads the
student, the attendance and the comment:

```json title="report-card.data.json"
{
  "report": {
    "term": "Autumn term 2026",
    "issued": "2026-12-18",
    "school": {
      "name": "Liffey Hill Secondary School",
      "street": "4 Ormond Quay Upper",
      "city": "Dublin 7"
    },
    "student": { "name": "Niamh Byrne", "number": "S-2291", "year": "Third year", "tutor": "Ms R. Doyle" },
    "attendance": { "present": 71, "sessions": 74 },
    "tutorComment": "Niamh has had a strong term. Her project work in science and art shows real care, and she now asks for help early when a topic doesn't click. Mathematics is the one subject to keep an eye on next term.",
    "subjects": [
      { "department": "Languages", "subject": "English", "teacher": "Mr P. Walsh", "score": 84, "comment": "Thoughtful essays; quote more from the text." },
      { "department": "Languages", "subject": "Irish", "teacher": "Ms S. Ní Bhriain", "score": 76, "comment": "Oral work much improved." },
      { "department": "Languages", "subject": "French", "teacher": "Mme C. Laurent", "score": 88, "comment": "Excellent listening and reading." },
      { "department": "Sciences", "subject": "Mathematics", "teacher": "Mr D. Kelly", "score": 63, "comment": "Algebra needs regular practice." },
      { "department": "Sciences", "subject": "Science", "teacher": "Dr A. Murphy", "score": 91, "comment": "Outstanding lab report on density." },
      { "department": "Humanities", "subject": "History", "teacher": "Mr J. O'Neill", "score": 79, "comment": "Good use of sources." },
      { "department": "Humanities", "subject": "Geography", "teacher": "Ms L. Kavanagh", "score": 72, "comment": "Fieldwork write-up was rushed." },
      { "department": "Arts", "subject": "Art", "teacher": "Ms E. Brennan", "score": 93, "comment": "A portfolio to be proud of." },
      { "department": "Arts", "subject": "Music", "teacher": "Mr T. Quinn", "score": 81, "comment": "Confident ensemble playing." }
    ]
  }
}
```

## What it renders

This isn’t a screenshot. quario compiles the definition and data above, and `@quario/viewer` renders
them in your browser, right here. The PDF button hands you the same report card as real
`@quario/pdf` bytes, one A4 page. The DOCX button beside it hands the same definition to
`@quario/docx`, where Word paginates and resolves the faces itself:

*Preview: the page renders this definition and its sample data live, with downloads — [see it rendered](https://getquario.com/templates/report-card).*

## Use it

```bash title="shell"
npm install quario @quario/pdf
```

A year group is one definition and one data document per student. Compile once, then render in a
loop:

```ts title="report-cards.ts"
import { writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import reportCard from './report-card.report.json' with { type: 'json' }

const report = quario({ locale: 'en-IE' }).report(reportCard)
const target = pdf({ page: { size: 'A4', margin: 54 } })

for (const card of await getReportCards('2026-autumn', 3)) {   // your student records
  writeFileSync(`report-card-${card.student.number}.pdf`, await report.render(target, { report: card }))
}
```

The issue date presents through the instance’s locale, `18 Dec 2026` under `en-IE`. The card holds
no money, so the instance needs no currency.

## Make it yours

Because the report card is declarative, changing it means changing data. A different grading scale
is the one expression in the _Grade_ column and the line that explains it. Effort marks are one more
field per subject and one more column. A comment per department is a group footer. A card per term
across a whole year is a group on the term with `"break": "before"`. It renders **without `eval`**,
so a teacher’s comment never becomes code. [Security →](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

### Can I print a whole year group at once?

Yes. Render one PDF per student, as the sample above does, and hand them to a print queue. For one
file to print in one run, put every student in one data document and add a group on the student
with `"break": "before"`.

### Can I export the grades to Excel?

Yes — `@quario/xlsx` renders the same definition to a workbook. Each department lands as its own
section, and every score is a real percentage the head of year can sort and average. [Full example
→](https://getquario.com/guides/json-to-excel.md)

### Can I use this report card template commercially?

The definition is yours — the report definitions, data, and output you create with quario belong to
you. quario is free to evaluate with no time limit. Sending real report cards home is past
evaluation, so that needs a license for each production deployment. Its users, agents, and renders
are never counted. [Pricing →](https://getquario.com/pricing.md)

### Where are the other templates?

[Browse the template gallery →](https://getquario.com/templates.md) — certificates, resumes, summary reports, and more.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Templates","item":"https://getquario.com/templates"},{"@type":"ListItem","position":3,"name":"Report card","item":"https://getquario.com/templates/report-card"}]}
```
