Foliant

Templates / Meeting minutes

Meeting minutes

Attendees, agenda items with decisions, and an action table with owners and due dates. The kind of document an agent writes after a call and sends before anyone forgets.

1 page, A4. Rendered in 40 ms. 8 inputs, all optional.

Download .typ Open preview

Rendered Meeting minutes template, first page
First page, rendered from the source below with the sample inputs.

Inputs

Everything variable comes in through sys.inputs. Missing keys fall back to sensible defaults, so an agent can send only what it knows.

KeySample value
absent
JSON, 26 chars
[
  "Jonas Klein (Meridian)"
]
actions
JSON, 223 chars
[
  {
    "what": "Send revised checkout error copy",
    "who": "Tom",
    "due": "2 Oct"
  },
  {
    "what": "Confirm test participants and rooms",
    "who": "Priya",
    "due": "3 Oct"
  },
  {
    "what": "Update project plan with scope freeze",
    "who": "Mara",
    "due": "30 Sep"
  }
]
attendees
JSON, 103 chars
[
  "Lena Hartmann (Northwind)",
  "Mara Ehrlich (Meridian)",
  "Tom Weber (Meridian)",
  "Priya Nair (Northwind)"
]
date28 September 2026, 10:00 to 10:45
items
JSON, 527 chars
[
  {
    "title": "Milestone 2 review",
    "notes": "Key flows approved with minor copy changes. Checkout error states need one more round.",
    "decision": "M2 accepted; copy fixes bundled into M3."
  },
  {
    "title": "Usability test",
    "notes": "Six participants recruited. Sessions planned for the week of 6 October.",
    "decision": "Go ahead; Northwind provides two internal users."
  },
  {
    "title": "Launch date",
    "notes": "Marketing wants 15 November. Engineering estimates 10 November for feature complete.",
    "decision": "Keep 15 November, freeze scope after M3."
  }
]
locationVideo call
meetingPortal redesign, weekly sync
next5 October 2026, 10:00

Render it

After copying the template into your account:

curl -X POST https://api.foliant.dev/v1/render \
  -H "Authorization: Bearer $FOLIANT_API_KEY" \
  -H "Content-Type: application/json" \
  -o minutes.pdf -d @- <<'JSON'
{ "template": "minutes.typ",
  "inputs": {
    "absent": "[\"Jonas Klein (Meridian)\"]",
    "actions": "[{\"what\":\"Send revised checkout error copy\",\"who\":\"Tom\",\"due\":\"2 Oct\"},{\"what\":\"Confirm test participants and rooms\",\"who\":\"Priya\",\"due\":\"3 Oct\"},{\"what\":\"Update project plan with scope freeze\",\"who\":\"Mara\",\"due\":\"30 Sep\"}]",
    "attendees": "[\"Lena Hartmann (Northwind)\",\"Mara Ehrlich (Meridian)\",\"Tom Weber (Meridian)\",\"Priya Nair (Northwind)\"]",
    "date": "28 September 2026, 10:00 to 10:45",
    "items": "[{\"title\":\"Milestone 2 review\",\"notes\":\"Key flows approved with minor copy changes. Checkout error states need one more round.\",\"decision\":\"M2 accepted; copy fixes bundled into M3.\"},{\"title\":\"Usability test\",\"notes\":\"Six participants recruited. Sessions planned for the week of 6 October.\",\"decision\":\"Go ahead; Northwind provides two internal users.\"},{\"title\":\"Launch date\",\"notes\":\"Marketing wants 15 November. Engineering estimates 10 November for feature complete.\",\"decision\":\"Keep 15 November, freeze scope after M3.\"}]",
    "location": "Video call",
    "meeting": "Portal redesign, weekly sync",
    "next": "5 October 2026, 10:00"
  } }
JSON

Through MCP, render_document with template: "minutes.typ" and the same inputs; get_template or fetch lists the keys.

Source

template.typ, 31 lines
#let input(k, default: "") = sys.inputs.at(k, default: default)
#let j(k) = json(bytes(input(k, default: "[]")))
#set page(paper: "a4", margin: 22mm, footer: context [#set text(size: 8.5pt, fill: luma(110)); #input("meeting") #h(1fr) #counter(page).display()])
#set text(font: "Noto Sans", size: 10pt, fill: rgb("1a1917"))
#text(size: 20pt, weight: 700)[Minutes]
#v(1mm)
#text(size: 12pt)[#input("meeting")]
#v(1mm)
#text(fill: luma(110))[#input("date") · #input("location")]
#v(6mm)
#grid(columns: (1fr, 1fr), gutter: 10mm,
  [#text(size: 8pt, fill: luma(110), tracking: 1pt)[ATTENDEES] \ #j("attendees").join(", ")],
  [#text(size: 8pt, fill: luma(110), tracking: 1pt)[ABSENT] \ #if j("absent").len() == 0 [none] else [#j("absent").join(", ")]])
#v(8mm)
#for (i, it) in j("items").enumerate() [
  #block(below: 6mm)[
    #text(weight: 700)[#(i + 1). #it.title]
    #v(1.5mm)
    #it.notes
    #v(1.5mm)
    #block(fill: rgb("f3efe6"), inset: 8pt, width: 100%, radius: 2pt)[#text(size: 9.5pt)[*Decision:* #it.decision]]
  ]
]
#text(size: 8pt, fill: luma(110), tracking: 1pt)[ACTIONS]
#v(2mm)
#table(columns: (1fr, auto, auto), inset: 7pt, stroke: (x, y) => if y == 0 { (bottom: 0.8pt) } else { (bottom: 0.3pt + luma(200)) },
  [*What*], [*Who*], [*Due*],
  ..j("actions").map(a => (a.what, a.who, a.due)).flatten())
#v(8mm)
#text(fill: luma(110))[Next meeting: #input("next")]