I’m working on a Typst project aimed at creating a question bank that compiles university entrance exams from all regions in Spain. I want to build a modular, reusable, and flexible system to generate customized exam sheets or collect all the questions from a complete module.
Project structure
A central file with the preamble, where I define functions like question, section, criteria, answer, and the general document layout.
Separate files for each region, acting as databases with all the questions, subdivided into sections, answers, and criteria.
A main file where I want to gather specific questions based on filters like year, exam session, region, or module, and optionally toggle the display of answers and evaluation criteria.
What I have so far
The file structure mostly set up.
The functions question, part, criteria, answer working at a basic level.
What I need help with
How can I make each question automatically calculate its total score, by summing the value of the section it contains?
Is there a way in Typst to filter questions based on metadata, such as:
Year (e.g., "2025")
Session (e.g., "Ordinary")
Region (e.g., "Andalusia")
Module (e.g., "Biomolecules")
How can I conditionally show or hide criteria and answers, depending on a global setting (like a variable show-answers: true)?
I know this is quite an ambitious project, but I believe Typst has great potential for educational applications like this.
Here’s the current draft of my document (still very much a work in progress):
I unfortunately don’t have time right now for a detailed answer, but as a starting point I’d look at the packages here: “exam” search results on Universe. Maybe one does what you want or gives you inspiration.
I’m obviously biased, but my own package for this is Scrutinize 0.3.0: author exams with Typst (link goes to the forum announcement). It explicitly supports the broad features you need help with: calculating sums of points, associating questions with metadata for filtering, and supporting answers shown based on a setting.
What makes Scrutinize a bit opinionated is that each question is a heading. This supports subquestions naturally, but I know some people prefer lists and other non-heading structures for organizing their exams.
#import "../preamble.typ": *
#question(
// scoring: 1,
region: "Andalucía",
year: 2025,
session: "Ordinaria",
module: "Biomoléculas",
description: [
Los glóbulos rojos mantienen un contenido salino interno del 0,9 %, lo que es crucial para su función. Su membrana plasmática, compuesta en un alto porcentaje por fosfolípidos regula el equilibrio osmótico con el plasma sanguíneo. Considerando estos aspectos, razone:
],
section(scoring: 0.5)[
¿Qué ocurriría con estas células si se inyectara a un individuo una solución salina que hiciera que la concentración final de sales en sangre fuese del 2,2 %?
#criteria[
0.25 por cada respuesta correcta.
]
#answer[
Los glóbulos rojos del organismo se encontrarían en un medio hipertónico y saldría agua de ellos (plasmólisis o crenación) con riesgo de muerte celular
]
],
section(scoring: 0.5)[
¿Y si la concentración final de sales en sangre fuese del 0,01%?
#criteria[
0.25 por cada respuesta correcta.
]
#answer[
Los glóbulos rojos se encontrarían en un medio hipotónico y entraría agua en su interior, aumentando su volumen (turgencia) con el riesgo de estallido (citólisis)
]
],
section(scoring: 1)[
Indique la composición de los fosfolípidos y explique por qué su estructura los hace idóneos para formar membranas biológicas.
#criteria[
0.25 por cada respuesta correcta.
]
#answer[
Composición: una molécula de glicerina (glicerol), dos ácidos grasos y un ácido fosfórico unido a un aminoalcohol (alcohol) (0,4 puntos). Por su carácter anfipático las cabezas polares se orientan hacia el agua y las colas apolares hacia el lado opuesto, alejadas del agua, formando bicapas lipídicas (0,6 puntos)
]
],
)
#import "../preamble.typ": *
#let questions = (
question(
region: "Andalucía",
year: 2025,
session: "Ordinaria",
module: "Biomoléculas",
description: [
Los glóbulos rojos mantienen un contenido salino interno del 0,9 %, lo que
es crucial para su función. Su membrana plasmática, compuesta en un alto
porcentaje por fosfolípidos regula el equilibrio osmótico con el plasma
sanguíneo. Considerando estos aspectos, razone:
],
sections: (
section(
scoring: 0.5,
problem: "¿Qué ocurriría con estas células si se inyectara a un individuo una solución salina que hiciera que la concentración final de sales en sangre fuese del 2,2 %?",
criterion: "0.25 por cada respuesta correcta.",
answer: "Los glóbulos rojos del organismo se encontrarían en un medio hipertónico y saldría agua de ellos (plasmólisis o crenación) con riesgo de muerte celular",
),
section(
scoring: 0.5,
problem: "¿Y si la concentración final de sales en sangre fuese del 0,01%?",
criterion: "0.25 por cada respuesta correcta.",
answer: "Los glóbulos rojos se encontrarían en un medio hipotónico y entraría agua en su interior, aumentando su volumen (turgencia) con el riesgo de estallido (citólisis)",
),
section(
scoring: 1,
problem: "Indique la composición de los fosfolípidos y explique por qué su estructura los hace idóneos para formar membranas biológicas.",
criterion: "0.25 por cada respuesta correcta.",
answer: "Composición: una molécula de glicerina (glicerol), dos ácidos grasos y un ácido fosfórico unido a un aminoalcohol (alcohol) (0,4 puntos). Por su carácter anfipático las cabezas polares se orientan hacia el agua y las colas apolares hacia el lado opuesto, alejadas del agua, formando bicapas lipídicas (0,6 puntos)",
),
),
),
)
First of all, sorry for the delay in replying — the last two weeks of school are always chaotic.
Secondly, thank you so much, this is basically exactly what I was looking for. I’m going to try to respond to each of the things you mentioned:
Each file contains questions from a specific region, and the idea was to import them into a single document that would group all the questions together.
That’s meant to show the name of the school where I work, but I’ll probably change it to display something else.
I just didn’t notice — I know it should be lowercase, but I didn’t get an error.
Honestly, I’m not really sure.
I based this on an example I found and modified it from there. I didn’t know you could do it that way.
I wanted the content to always start on an odd page. From what you showed me, it looks like it’s possible to handle that directly.
My intention is to include it at some point. In the future, I’ll add other files similar to exam_year.typ organized by region, topic, or other categories — for example, something like this:
Some questions may not have sections — just a main prompt. So I’d like to be able to set a score manually when needed.
No real reason… I guess it’s just a habit, haha.
Originally I was planning to separate the exercise number from the prompt, and I wanted it to always take up the same amount of space regardless of the number.
I was trying different styles. I didn’t know that you could adjust weight using strong.
They got deleted and I didn’t notice, honestly.
I took that from another forum post and thought I might use it. But you’re right — I don’t need a filter in each question bank individually, just in the document that combines them all.
This is true — each section should have a single criterion and answer, so it makes more sense to include them as parameters within section.
I plan to keep the documents locally, and I often use the file search feature instead of navigating through folders.
Thanks again, this is exactly what I needed.
One more question: would it be possible to select just some specific sections from a question that has multiple sections?
I suppose I’d need to modify the question function to allow filtering a specific question:
/// Create a question.
#let question(
region: none,
year: none,
session: none,
module: none,
position: none,
description: "Question description",
sections: (),
) = {
let metadata = (
region: region,
year: year,
session: session,
module: module,
)
for (k, v) in metadata { assert(v != none, message: k + " is not set") }
(metadata: metadata, description: description, sections: sections)
}
And, of course, I guess I’d need to do something similar for section:
I’ve modified it so that it works in such a way that if there are no sections, it takes the scoring value from the question. Additionally, it allows adding the criterion and the answer. Let me know what you think.
I was referring to being able to filter within sections as well, so that each section has an identifier and, when displaying a question, I can choose which section to show. I have the following question:
Initially, I saw this as a quick way to export a set of questions with a specific typology (year, region, etc.). Now I also see the possibility of using it to generate my exams by adding an identifier to each question and each section. However, for this, I would need to write the previous code
for every question I want to include, and also keep in mind that the scoring varies depending on the sections I choose.