Problem format (manual ZIP)
This page is for the experienced problemsetter who needs to hand-build a problem
.zip — or edit one omegaUp already deployed — because they need something the
point-and-click tools do not expose: Karel problems, interactive tasks,
a custom validator, or precise control over grouping and weights. If you are
just getting started, or your problem is a plain "read input, print output" task,
use the Problem Creator (CDP) instead and
save yourself the packaging — there is a CDP walkthrough on YouTube.
Video walkthroughs
If you do go the manual route, it helps to watch someone do it first: part 1 and part 2 of the manual problem-creation tutorial.
The single most important thing to internalize before you build anything: the
.zip you upload is not what omegaUp stores. When you upload, omegaUp's
gitserver (the Go service in omegaup/gitserver
that keeps every problem as its own git repository) unpacks the archive, reads
your cases/, your optional testplan, and any settings.json you included,
and compiles all of it down into one canonical settings.json that the
grader actually consumes. testplan and any partial settings.json you shipped
are deleted after they are folded in, precisely because they are now redundant
with the generated file (see
ziphandler.go).
So think of the .zip as source and settings.json as the compiled artifact —
which is exactly why the directory names and file extensions below have to be
letter-perfect.
The configurable settings (mental model)
Whether you set these through the web UI or ship them in packaged metadata, every problem carries the same handful of knobs. Understanding what each one means — and what verdict you get when it is exceeded — is what lets you package correctly.
Validator: how the contestant's output is judged
The validator decides whether an output is right, and gives a per-case score in
[0.0, 1.0]. omegaUp ships five, whose canonical names live in
ProblemParams.php
(PHP side) and
common/problemsettings.go
(grader side):
token— token-by-token. It reads every token (a run of up to 4,194,304 contiguous printable characters — 4 MiB, theMaxTokenLengthin the runner'stokenizer.go— separated by whitespace) from both the expected.outand the contestant's output, and requires the two token sequences to be identical. This is the default and what you want for almost everything.token-caseless— same astoken, but it lowercases every token first, soYesandyesmatch. Reach for this when capitalization is not part of the answer.token-numeric— reads numeric tokens only, interprets them as numbers, and accepts them when the contestant's value is within an absolute or relative error of 1e-9 of the expected value (the defaultTolerance, also1e-9, set inProblem.php's default settings). The two sequences must still have the same length. Use it for floating-point answers where the last digits are allowed to wobble.literal(shown in the UI as "interpret standard output as score") — reads the contestant's stdout, parses it as a single float, and clamps it to[0.0, 1.0]to use directly as the case score. It is almost exclusively for interactive problems: the interactor process, not the contestant, prints the score, which stops the contestant from simply printing1.0to cheat.custom(validator.<lang>) — you ship a program that reads the contestant's stdout (and, if it wants, the case's input and expected output) and prints the score itself. Full details and worked examples are in Custom validator below.
Languages: what the contestant may submit
- C, C++, Java, Python, … — the contestant submits source in one of omegaUp's supported languages.
- Karel — the contestant submits a Karel program. See the Karel problems section for how to build the cases.
- Output only — the contestant uploads a
.zipof answers for every case instead of a program. If you want to also let them paste a single case's answer as plain text rather than a zip, the problem must have exactly one case namedMain.in/Main.out. - No submissions — the contestant cannot submit at all. This exists purely to display content (a reading, a lesson) inside a course.
Time, memory, and output limits
Each of these maps to a specific verdict when the contestant's program crosses it,
and each has a real default. The problem-creation form currently pre-fills these
values (see
Problem.php),
and the grader's own DefaultLimits
(common/problemsettings.go)
agree with them, so a package that omits limits still runs sensibly:
- Time limit —
TimeLimit(ms), default1000— the maximum CPU time the OS lets the contestant's process run for each case before it is killed withTLE. This is CPU time, not wall-clock, so time spent asleep or blocked does not count against it. - Total (overall wall) time limit —
OverallWallTimeLimit(ms), default60000— the maximum real time the grader waits for the whole problem to finish before stopping it withTLE. Any case that did not get to run before this deadline is simply not evaluated. To keep results at least somewhat consistent when this fires, cases are evaluated in lexicographic order, so which cases get skipped is deterministic rather than random. - Memory limit —
MemoryLimit(KiB), default32768(i.e. 32 MiB) — the maximum RAM (heap + stack) the OS lets the program use before killing it withMLE. It is expressed in kibibytes, so32768KiB = 32 MiB. - Output limit —
OutputLimit(bytes), default10240— the most the program may write to stdout or stderr before it is killed withOLE. For ordinary token problems omegaUp normally auto-detects this from your.outfiles — it takes the largest one and adds 10 KiB of headroom — so you rarely set it by hand. But if you use a custom validator you must set it explicitly, because there is no plain.outto size against. - Input limit —
inputLimit(bytes), default10240— the maximum length of the contestant's source code. Turn this down when you want to stop people from pasting in a precomputed answer table instead of actually solving the problem. - Validator time limit —
validatorTimeLimit(ms), default1000— how long the grader waits for a custom validator to emit a verdict for each case before giving up withJE. - Extra wall time for libinteractive —
ExtraWallTime(ms), default0— how long the grader waits for the interactor program to finish each case (beyond the normal limits) before stopping it withTLE. Only relevant to interactive problems.
Custom validators get their own, more generous limits
The moment you switch the validator to custom, omegaUp seeds a separate set
of limits for the validator process — currently 256 MiB memory, a 30 s
CPU time limit, 5 s overall wall time, and 10 KiB output
(Problem.php).
The idea is that judging can afford to be slower and hungrier than the
contestant's solution.
Everything else
- Source — attribution/origin for the statement, shown to contestants.
- Appears in the public listing — whether the problem can be shown publicly and used in other people's contests and courses.
- Send clarifications by email — whether omegaUp emails you (the author) when a user asks a clarification about this problem.
- Tags — classification labels.
The ZIP layout
Save everything into a .zip archive — not .rar, .tar.bz2, .7z, or
.zx. The name of the zip itself does not matter. A minimal language problem
looks like this:
problem.zip
├── cases/ # Required: the .in/.out test data
│ ├── 1.in
│ ├── 1.out
│ └── …
├── statements/ # Required: at least one <locale>.markdown
│ └── es.markdown
├── solutions/ # Optional: editorial / official write-up
├── interactive/ # Optional: libinteractive bundle
├── validator.cpp # Optional: custom validator (one of validator.<lang>)
├── settings.json # Optional: pre-baked settings (usually generated for you)
└── testplan # Optional: per-case weights
A real, working reference bundle lives in the repo at
frontend/tests/resources/testproblem.zip,
and there are many more under
frontend/tests/resources.
cases/ and statements/ must sit at the very root
It is critically important that cases/ and statements/ are directly at
the root of the .zip, with no intermediate folder wrapping them — this
has bitten enough people to earn its own bug,
omegaup#310. On Linux/Mac the
reliable way to get this right is to cd into the problem directory and run
zip -r myproblem.zip *, which zips the contents rather than the containing
folder. And because omegaUp runs on Linux, names are case-sensitive: a
folder called Cases will not be found, and neither will an input file ending
in .In instead of .in.
cases/
This folder holds every test case as paired .in/.out files. The base names
must match — 1.in with 1.out, hola.in with hola.out — but the base name
itself is arbitrary. Internally the deployer only accepts files matching the
regexp ^cases/([^/]+)\.in$
(ziphandler.go),
and if the folder is missing or empty the upload fails outright with
cases/ directory missing or empty
(ziphandler.go).
Every .in that expects submissions must have a matching .out, or the deploy
errors with failed to find the output file for cases/<name>.
The . (dot) in a case name is reserved for grouping. Do not put a dot in a
case name unless you mean to group — the text before the first dot becomes the
group name (strings.SplitN(caseName, ".", 2)[0] in
common/problemsettings.go).
So grupo1.caso1.in, grupo1.caso1.out, grupo1.caso2.in, grupo1.caso2.out
form one group (grupo1) with two cases.
Grouped cases exist because sometimes the set of plausible answers is tiny and you do not want a contestant scoring partial credit on a lucky guess: to earn a group's points you must solve every case in that group. There is no limit on the number of groups, and groups may have different numbers of cases.
There is no hard limit on the number of cases either, but keep the total case
payload under ~100 MB. More cases means every submission takes longer to grade,
and in a live contest that translates directly into queue wait times — especially
painful when a slow, TLE-bound solution is sitting ahead of everyone else in the
queue.
statements/
This holds the problem statement in Markdown (the same flavor Wikipedia uses), one
file per locale: es.markdown, en.markdown, pt.markdown. At least one is
required. You can preview exactly how your Markdown and LaTeX will render at
omegaup.com/redaccion.php — please actually do
this and confirm the input/output tables look right, because a garbled statement
is a miserable experience mid-contest.
LaTeX is fully supported. Wrap variable names in $…$ — write $n$, $x$,
$x_i$ for a subscript — so they stand out from prose and contestants can find
them at a glance. It reads better and it avoids ambiguity.
solutions/
Structurally identical to statements/: the official solution write-up in
Markdown, named per locale (es.markdown, and translations en.markdown,
pt.markdown). The bundle at
testproblem.zip
includes a solutions example.
interactive/ (optional)
Interactive problems — where the contestant's program talks back and forth with a
judge process rather than reading a fixed input — must be built with
libinteractive; that page documents the
.idl interface format and how the shims are generated. For a complete, real
reference of how an interactive problem's zip is structured, use
Cave from IOI 2013 as a template.
One convenience the deployer handles for you: libinteractive sample cases under
interactive/examples/ do not need an .out — gitserver generates an empty
one automatically
(ziphandler.go).
Custom validator (validator.<lang>)
When token comparison is not enough — multiple correct answers, special-judge
scoring, partial credit — ship exactly one file named validator.<lang> at
the root of the zip, where <lang> is one of c, cpp, java, p (Pascal),
or py. You only need one validator, and it is independent of the contestant's
language.
Here is the exact contract, and it pays to get it right:
- Your validator reads the contestant's output on its own stdin — plain
scanf/cin/input(). Mentally, the grader runs the equivalent of./contestant < data.in | ./validator <casename>, where<casename>is the current case's.inname without the extension. - It may open a file literally named
data.in— the same input that was fed to the contestant — and a file nameddata.out— the expected output paired with thatdata.in. Read either, both, or neither. - It must print a single float in
[0.0, 1.0]to stdout — the fraction of the case the contestant got right. Print nothing →JE. Print less than 0 → the score is clamped to 0; print more than 1 → clamped to 1. - The validator runs inside the same sandbox as contestant programs. If the
validator itself misbehaves (
WA,RFE,RTE, …), the submission is judgedJE— so a buggy validator fails loudly rather than silently mis-scoring. - You must still ship
.outfiles even though they will not be used for comparison. Empty files are fine; they just have to exist so the case pairing succeeds.
A validator for sumas (read two
integers, print their sum) in C++17 — note how it reads the original a and b
from data.in, the expected sum from data.out, the contestant's answer from
stdin, and prints 1.0 or 0.0:
#include <iostream>
#include <fstream>
int main() {
// read "data.in" to get the original input.
int64_t a, b;
{
std::ifstream original_input("data.in", std::ifstream::in);
original_input >> a >> b;
}
// you can store anything that helps you evaluate in "data.out".
int64_t sum;
{
std::ifstream original_output("data.out", std::ifstream::in);
original_output >> sum;
}
// read standard input to get the contestant's output.
int64_t contestant_sum;
if (!(std::cin >> contestant_sum)) {
// anything you print to cerr is ignored, but it's useful for debugging.
std::cerr << "Error reading the contestant's output\n";
std::cout << 0.0 << '\n';
return 0;
}
// determine whether the answer is incorrect.
if (sum != contestant_sum && sum != a + b) {
std::cerr << "Incorrect output\n";
std::cout << 0.0 << '\n';
return 0;
}
// If execution reaches here, the contestant's output is correct.
std::cout << 1.0 << '\n';
return 0;
}
The same validator in Python 3:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import logging
import sys
def _main():
# read "data.in" to get the original input.
with open('data.in', 'r') as f:
a, b = [int(x) for x in f.read().strip().split()]
# you can store anything that helps you evaluate in "data.out".
with open('data.out', 'r') as f:
expected_sum = int(f.read().strip())
score = 0
try:
# Read the contestant's output.
contestant_sum = int(input().strip())
# Determine whether the output is incorrect.
if contestant_sum not in (expected_sum, a + b):
# Anything printed to sys.stderr is ignored, but useful for debugging.
print('Incorrect output', file=sys.stderr)
return
# If execution reaches here, the contestant's output is correct.
score = 1
except:
logging.exception("Error reading the contestant's output")
finally:
print(score)
if __name__ == '__main__':
_main()
testplan (optional)
By default every case is worth 1/number-of-cases — the deployer assigns each
case a weight of 1/1 and the grader normalizes all weights so they sum to 1
(AddCaseName(caseName, big.NewRat(1, 1), false) in
ziphandler.go,
weights divided by the total in
common/literalinput.go).
When you want cases weighted unequally, drop a file named testplan (no
extension) at the root of the zip, one line per case: the case's file name
without the extension, whitespace, then the number of points. For a problem
with cases cases/caso1.in, cases/grupo2.caso1.in, cases/grupo2.caso2.in:
caso1 5
grupo2.caso1 10
grupo2.caso2 0
A few things the parser
(NewCaseWeightMappingFromTestplan)
actually enforces, matching each line against
^\s*([^#[:space:]]+)\s+([0-9.]+)\s*$:
- No spaces in case file names — the case name token cannot contain whitespace.
#starts a comment — a line whose first non-space character is#(and any line that does not match the pattern) is skipped, so you can annotate your testplan.- The
testplanand the.zipmust agree on the set of cases. gitserver runs a symmetric difference both ways (ziphandler.go): a case in the testplan but missing fromcases/fails withtestplan missing case "<name>", and a case incases/but absent from the testplan fails with.zip missing case "<name>". You cannot half-specify.
To score a whole group without splitting the points across its cases, the
convention is to put the group's full score on the first case and 0 on all
the others — as grupo2.caso1 10 / grupo2.caso2 0 does above.
This interacts with the group's score policy, one of two values in
ProblemParams.php:
sum-if-not-zero (the default — a group scores the sum of its cases' scores,
but only if they are all non-zero) or min (the group scores the minimum of
its cases' scores times the group weight). The default is why the
"points-on-the-first-case, zero-on-the-rest" convention works: solve the whole
group and you collect the full weight; miss any case and the group collapses to
zero.
settings.json (usually generated, occasionally hand-written)
You will most often never write this file — it is the compiled artifact gitserver
produces from your cases/, testplan, and limits, marshaled out in
ziphandler.go.
Its shape is the ProblemSettings struct
(common/problemsettings.go):
a Limits block, a Validator block (Name, Tolerance, optional
GroupScorePolicy, optional custom-validator Limits), a Cases array of groups
each with their weighted cases, and — for interactive problems — an Interactive
block. If you do ship your own settings.json, gitserver reads it, then still
lets a testplan override the case weights on top of it. Either way, only the
generated settings.json survives into the deployed problem repository.
Images
omegaUp has native image support :). To embed an image in a statement, add the
image file to your zip inside statements/ and reference it from your
es.markdown with ordinary Markdown:

Supported formats are jpg, gif, png. Mind the size — Markdown will not rescale it — so keep images at or under 650 pixels wide.
Example zips
The zips omegaUp uses in its own tests are the best templates to copy from:
frontend/tests/resources.
Karel problems
First, just try karel.js — it converts cases for you and is far less trouble than what follows.
If you already have your cases and would rather not re-make them in karel.js, the
steps below are for Windows and assume you have Python 2.7 installed and on
your PATH (the default install path is typically C:\Python27); verify you can
run python from the DOS console before starting.
- Have these files on hand:
the Karel toolkit —
karel.exe(runs a solution against a world),kcl.exe(the solution compiler), the Python scriptkarel_mdo_convert.py, and the wrapperkarel-to-omegaup.batthat ties them together. - Put your MDO and KEC cases in one folder. To generate them you can use the case-making Karel from KarelOMI.zip.
- You also need your solution. If you program in Java, give the solution a
.JSextension (sokcl.exeinterprets it as karel-java); for Pascal, use.PAS(karel-pascal). - Put the exes, the Python script, and the
.batall in that same folder. - Run
karel-to-omegaup.batwith no arguments and it will prompt for the solution path and the cases path, or pass them on the command line — quote paths that contain spaces:
karel-to-omegaup.bat "karel vs chuzpa\solucion.js" "karel vs chuzpa\casos"
- With everything in place, the script first compiles the solution with
kcl.exe(producing a.KX), then builds the.INworlds from every.MDOin the cases folder. Note the Python converter needs the matching.KECto exist: forcaso1.MDOyou must also havecaso1.KEC. From those it extracts beepers, orientation, and position into each.IN. - It then runs
karel.exewith each generated.INand the compiled.KXsolution to produce the matching.OUT— so a correct solution is essential, since the outputs are only as right as it is. - The
.batdrops acasesfolder (with the.IN/.OUTpairs) inside your cases directory. - Finally, add a
statementsfolder withes.markdownand zip it up exactly as you would a language problem.
How it all comes together
To close the loop: when you upload, gitserver's
ziphandler.go
unpacks the archive, validates that cases/ exists and every submitting case has
its .out, folds testplan/settings.json into one canonical settings.json,
commits the whole thing as a new revision of the problem's git repository, and
deletes the now-redundant testplan. At grade time the PHP frontend
(\OmegaUp\Controllers\Run::apiCreate →
\OmegaUp\Grader::grade)
hands the submission to the Go grader over HTTP, which reads that settings.json,
normalizes the case weights to sum to 1, runs each case under the sandbox against
your limits, applies the validator, and rolls the per-case scores up through the
group score policy. Every path and extension in this document exists to make that
pipeline resolve correctly — which is why getting them exactly right matters.
Related documentation
- Creating problems — the authoring workflow and UI paths
- Verdicts — what
AC,TLE,MLE,OLE,JE, and the rest mean