---
title: "Timesheet template for JavaScript — quario"
description: "A ready-made quario timesheet for JavaScript. Hours per day and project, grouped by week, with billable and total hours and a sign-off. Copy the definition, point it at your time entries, and render to Excel, PDF, HTML, or Word."
url: "https://getquario.com/templates/timesheet"
---

# Timesheet template for JavaScript

A timesheet is where the hours start. Payroll pays from it, and the client invoice bills from it.
The person who approves it wants a week at a time, with a total under each week. Finance wants the
billable hours apart from the rest. Most of the time, they want it as a spreadsheet they can sum
again. This is a ready-made quario timesheet — copy it, point it at your time entries, and render to
Excel, PDF, HTML, or Word.

## The template

The whole timesheet is a definition — data you can version and review. The employee and employer
blocks, the weeks, the totals and the two signature lines are all in here. This is the exact file
the preview below renders:

```json title="timesheet.report.json"
{
  "data": "$.timesheet.entries[*]",
  "sort": [{ "by": "=@.date" }],
  "aggregates": {
    "hours": "sum:=@.hours",
    "billable": "sum:=@.billable ? @.hours : 0",
    "days": "countDistinct:=@.date"
  },
  "style": { "family": "sans", "size": 10 },
  "header": [
    {
      "type": "split",
      "style": { "spaceAfter": 18 },
      "slots": [
        { "type": "image", "source": "=logo()", "fit": "width", "alt": "{{ $.input.timesheet.employer.name }} logo", "width": 28 },
        { "type": "text", "value": "", "style": { "size": 8 } }
      ]
    },
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "Timesheet", "width": 50,
          "style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": [{ "value": "{{ $.input.timesheet.from }}" }, { "value": " to " }, { "value": "{{ $.input.timesheet.to }}" }],
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "split",
      "style": { "spaceAfter": 4 },
      "slots": [
        { "type": "text", "value": "No. {{ $.input.timesheet.number }}", "width": 50,
          "style": { "size": 8, "bold": false, "color": "#989a97" } },
        { "type": "text", "value": [{ "value": "Submitted " }, { "value": "{{ $.input.timesheet.submitted }}" }],
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 50 }, {}],
      "rows": [
        {
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97", "paddingTop": 22 },
          "slots": [{ "type": "text", "value": "Employee" }, { "type": "text", "value": "Employer" }]
        },
        {
          "style": { "size": 10, "bold": true, "color": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "{{ $.input.timesheet.employee.name }}" },
            { "type": "text", "value": "{{ $.input.timesheet.employer.name }}" }
          ]
        },
        {
          "style": { "size": 10, "bold": false, "color": "#5e6062", "paddingBottom": 14 },
          "slots": [
            { "type": "text", "value": "{{ $.input.timesheet.employee.role }}\nNo. {{ $.input.timesheet.employee.number }}" },
            { "type": "text", "value": "{{ $.input.timesheet.employer.street }}\n{{ $.input.timesheet.employer.city }}" }
          ]
        }
      ]
    }
  ],
  "groups": [
    {
      "name": "week",
      "by": "=@.week",
      "aggregates": { "hours": "sum:=@.hours", "from": "first:=@.date", "to": "last:=@.date" },
      "header": [
        {
          "type": "split",
          "style": { "spaceBefore": 10, "spaceAfter": 2 },
          "slots": [
            { "type": "text", "value": "Week {{ week.key }}", "width": 50,
              "style": { "size": 11, "bold": true, "color": "#1e1f22" } },
            { "type": "text", "value": [{ "value": "{{ week.from }}" }, { "value": " to " }, { "value": "{{ week.to }}" }],
              "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
          ]
        }
      ],
      "footer": [
        {
          "type": "split",
          "style": { "paddingTop": 5, "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#dcdcdc" },
          "slots": [
            { "type": "text", "value": "Week {{ week.key }} total", "width": 84,
              "style": { "size": 8, "color": "#989a97", "align": "right" } },
            { "type": "text", "value": "{{ week.hours }}",
              "style": { "color": "#1e1f22", "align": "right", "format": { "kind": "number", "digits": 2 } } }
          ]
        }
      ]
    }
  ],
  "detail": {
    "header": {
      "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
        "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
    },
    "row": { "style": { "color": "#5e6062" } },
    "columns": [
      { "header": "Date", "value": "{{ @.date }}", "width": 16, "style": { "format": "date" } },
      { "header": "Project", "value": "{{ @.project }}", "width": 28, "style": { "color": "#1e1f22" } },
      { "header": "Task", "value": "{{ @.task }}", "width": 30 },
      { "header": "Billable", "value": "{{ @.billable ? 'Yes' : 'No' }}", "width": 10, "style": { "color": "#989a97" } },
      { "header": { "value": "Hours", "style": { "align": "right" } }, "value": "{{ @.hours }}", "width": 16,
        "style": { "align": "right", "color": "#1e1f22", "format": { "kind": "number", "digits": 2 } } }
    ]
  },
  "footer": [
    {
      "type": "grid",
      "columns": [{ "width": 84, "style": { "align": "right" } }, { "style": { "align": "right", "format": { "kind": "number", "digits": 2 } } }],
      "rows": [
        {
          "style": { "color": "#5e6062", "paddingTop": 12 },
          "slots": [{ "type": "text", "value": "Billable" }, { "type": "text", "value": "{{ $.billable }}" }]
        },
        {
          "style": { "color": "#5e6062" },
          "slots": [{ "type": "text", "value": "Non-billable" }, { "type": "text", "value": "{{ $.hours - $.billable }}" }]
        },
        {
          "style": { "size": 11, "bold": true, "color": "#1e1f22", "paddingTop": 6, "paddingBottom": 4,
            "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
          "slots": [{ "type": "text", "value": "Total hours, {{ $.days }} days" }, { "type": "text", "value": "{{ $.hours }}" }]
        }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 45 }, { "width": 10 }, {}],
      "rows": [
        {
          "style": { "paddingTop": 34 },
          "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" } }
          ]
        },
        {
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97", "paddingTop": 4 },
          "slots": [
            { "type": "text", "value": "Employee · {{ $.input.timesheet.employee.name }}" },
            { "type": "text", "value": "" },
            { "type": "text", "value": "Approved · {{ $.input.timesheet.approver }}" }
          ]
        }
      ]
    }
  ],
  "page": {
    "footer": [
      {
        "type": "split",
        "style": { "paddingTop": 10, "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
        "slots": [
          { "type": "text", "value": "Hours as recorded by the employee. Approval confirms them for payroll and client billing.", "width": 80,
            "style": { "size": 8, "color": "#989a97" } },
          { "type": "text", "value": "Page {{ page.number }} of {{ page.total }}",
            "style": { "size": 8, "color": "#989a97", "align": "right" } }
        ]
      }
    ]
  }
}
```

The weeks are a **group** on `=@.week`. Each one carries its own aggregates: `week.hours` sums that
week’s entries, and `week.from` and `week.to` are its first and last date. The group header reads
them as _17 Aug 2026 to 21 Aug 2026_, and the group footer prints the week’s total. The report
`sort` on the date is what keeps both the weeks and the days in order.

Billable hours are one aggregate, `sum:=@.billable ? @.hours : 0`. Non-billable hours are the total
less that, in the footer. Nothing about the split lives in your code, so a different rule is a
different expression in the file.

Hours present through `{ "kind": "number", "digits": 2 }`, so `7.25` reads _7.25_ and `6` reads
_6.00_. The cell still stores the bare number. That matters in the workbook, where a total row has
to sum.

## The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. `$.timesheet.entries[*]` in the definition selects its rows, and `$.input` reads the
period, the people and the approver:

```json title="timesheet.data.json"
{
  "timesheet": {
    "number": "TS-2026-34",
    "from": "2026-08-17",
    "to": "2026-08-30",
    "submitted": "2026-08-31",
    "employer": {
      "name": "Quire Press Ltd.",
      "street": "Binnenkant 42",
      "city": "1011 BM Amsterdam"
    },
    "employee": {
      "name": "S. Vink",
      "number": "10417",
      "role": "Compositor"
    },
    "approver": "M. de Graaf",
    "entries": [
      {"week": 34, "date": "2026-08-17", "project": "Folio & Sons catalogue", "task": "Typesetting, sections 1–3", "hours": 6, "billable": true},
      {"week": 34, "date": "2026-08-17", "project": "Internal", "task": "Press maintenance", "hours": 1.5, "billable": false},
      {"week": 34, "date": "2026-08-18", "project": "Folio & Sons catalogue", "task": "Typesetting, sections 4–6", "hours": 7.5, "billable": true},
      {"week": 34, "date": "2026-08-19", "project": "Folio & Sons catalogue", "task": "Proof corrections", "hours": 4, "billable": true},
      {"week": 34, "date": "2026-08-19", "project": "Type specimen reprint", "task": "Plate check", "hours": 3.5, "billable": true},
      {"week": 34, "date": "2026-08-20", "project": "Type specimen reprint", "task": "Make-ready and press run", "hours": 6.5, "billable": true},
      {"week": 34, "date": "2026-08-20", "project": "Internal", "task": "Team meeting", "hours": 1, "billable": false},
      {"week": 34, "date": "2026-08-21", "project": "Folio & Sons catalogue", "task": "Index and cross-references", "hours": 6.5, "billable": true},
      {"week": 35, "date": "2026-08-24", "project": "Folio & Sons catalogue", "task": "Second proof", "hours": 7.5, "billable": true},
      {"week": 35, "date": "2026-08-25", "project": "Type specimen reprint", "task": "Press run", "hours": 7.25, "billable": true},
      {"week": 35, "date": "2026-08-26", "project": "Type specimen reprint", "task": "Binding check", "hours": 3, "billable": true},
      {"week": 35, "date": "2026-08-26", "project": "Internal", "task": "Training, new guillotine", "hours": 4.5, "billable": false},
      {"week": 35, "date": "2026-08-27", "project": "Folio & Sons catalogue", "task": "Final files to print", "hours": 6, "billable": true},
      {"week": 35, "date": "2026-08-27", "project": "Internal", "task": "Press maintenance", "hours": 1.5, "billable": false},
      {"week": 35, "date": "2026-08-28", "project": "Folio & Sons catalogue", "task": "Press check", "hours": 5.75, "billable": true}
    ]
  }
}
```

The entries are flat, one line per task per day, with the week number on the row. That’s the shape a
time-tracking table already has, and the group does the nesting on the page.

## 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 timesheet as real `@quario/pdf`
bytes, one A4 page with its page footer on the baseline. 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/timesheet).*

## Use it

The workbook is the render most timesheets end up as, so this one uses `@quario/xlsx`:

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

```ts title="timesheet.ts"
import { readFileSync, writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { xlsx } from '@quario/xlsx'
import timesheet from './timesheet.report.json' with { type: 'json' }

const logo = () => readFileSync('logo.png')          // your logo, as PNG bytes
const report = quario({ locale: 'en-IE' }).report(timesheet, { logo })

const data = await getTimesheet('10417', '2026-08-17')   // your time entries
const bytes = await report.render(xlsx(), data)

writeFileSync('timesheet.xlsx', bytes)
```

Each hours cell lands in the sheet as a number with a two-digit number format. So a payroll clerk can
select a week and read its sum in the status bar. The dates land as typed dates that present through
the instance’s locale. Swap `xlsx()` for `pdf()` from `@quario/pdf` and the same definition prints the
signed copy. See [JSON to Excel](https://getquario.com/guides/json-to-excel.md) for the workbook target in full.

## Make it yours

Because the timesheet is declarative, changing it means changing data. Overtime is one more
aggregate, the hours over the contract, and one more row in the footer. A column per day instead of
a row per entry is a different shape of data with the same totals. A month per sheet is
`"break": "before"` on a month group. A rate per project turns the billable hours into an amount in
the footer, under `format: "currency"`. It renders **without `eval`**, so a task description typed
by an employee never becomes code. [Security →](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

### Can I export the timesheet to Excel?

Yes, and that’s the render the sample above uses. `@quario/xlsx` writes each week as its own section
with its header row and its total, and every hours cell is a real number. A finance team can sum,
filter, and pivot it without retyping anything. [Full example →](https://getquario.com/guides/json-to-excel.md)

### Can I generate it as a PDF to sign?

Yes. `@quario/pdf` renders this definition straight to bytes, with the two signature lines at the
foot of the last page and no browser anywhere. A Word render through `@quario/docx` gives a copy the
employee can still correct before it’s signed. [Full example →](https://getquario.com/guides/json-to-pdf.md)

### Can I use this timesheet 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. Running payroll or billing from the timesheet 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) — payslips, work orders, 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":"Timesheet","item":"https://getquario.com/templates/timesheet"}]}
```
