draftply Download

Reference

How to write a sheet, every function, and the app around it.

Start here

At a glance

Type one calculation per line; the result appears next to it. Lines without an operator are notes and stay quiet, // starts a comment and # a heading. This is most of what you need:

1# Trip
2price = 1.85 €/L // a variable with a unit1.85 €/L
3fuel = 40 L * price // units cancel on their own74 €
4fuel + 19% // a markup88.06 €
5VAT: fuel * 19% // a label names the resultVAT:14.06 €
6@3 * 2 // the result of line 3148 €
7Fuel costs {fuel} in all.Fuel costs 74 € in all.
8
912.5012.5
108.008
11total // sums the lines above20.5

Where to look next:

  • New here? The Quick start has copy-ready examples for everyday tasks: VAT, discounts, units, loans, dates.
  • Something about the syntax — Writing a sheet: notes, variables, total, percentages, units, text, dates, lists.
  • A specific function — use the search box (press /), or the Free and Pro table, which lists every function by topic.
  • Charts, equations, loops, forms — the Pro tools.
  • The app itself — windows, drafts, shortcuts, settings: The app.

Free and Pro

Everything not listed under Pro is free, on every platform. A Pro function in a free version shows · Pro instead of a result; the rest of the sheet keeps calculating.

TopicFree
Writing a sheetnotes, // comments, # headings, labels, {…} in text, variables, @n, total
Numbers+ - / ^ %, += -= = /=, k/M, percentages, comparison & logic, a if c else b
Unitsunits and currencies inline, in/to, convert, defunit
Mathsqrt, cbrt, abs, exp, log, ln, log10, log2, round, floor, ceil, min, max, clamp, sign, pow, mod, hypot, lerp, proportion
Trigonometrysin … atanh, atan2, deg2rad, rad2deg
Combinatorics · Randomfactorial, gcd, lcm, nCr, nPr · rand, randi
Number systems0x/0b/0o, hex, bin, oct, dec, band, bor, bxor, bnot, shl, shr
Complex numbers3i, re, im, conj, arg, roots
Electronicsdb, db2ratio, dbm, dbm2w, e12, e24
Text · Dates"…", +, s[i], str, ord, char, print · date, datetime, today, now, days, addmonths, dformat, …
Lists · Matriceswriting them, [i], arithmetic ([1, 2] * 2)
CSV · Filescsv, csvwrite · import, share, open-with
TopicPro
List functionslen, range, push, pop, first, last, sort, reverse, contains, list, append, keys, values, map, filter, linspace, cumsum, cumprod (also on text)
Matrix functionsdet, inv, transpose, trace, norm, eye, zeros, ones, size
Statisticssum, mean, median, std, variance, mode, mad, percentile, quantile, corr, cov, linreg
Financepv, fv, pmt, nper, rate, npv, irr
Chartsplot, scatter, bar, pie, hist, surface, scatter3
Algebra · Calculussolve, simplify, factor · diff, integrate
Programmingf(x) = …, def, for/while/if, class
Form view · Offer exporta sheet as a fillable form · a sheet as a PDF quote

sum is a statistics function and therefore Pro — to add up a column for free, write total below it.

Writing a sheet

Lines, notes and comments

How a line is read

Every line is read on its own and gets its result in the column next to it (or behind the text in narrow windows):

  • A calculation — 3 * 4, rent = 1200, sqrt(2) — shows its result.
  • A note — a line without operators, such as Shopping for Saturday — shows nothing. A built-in function name with a number and no brackets (sqrt 16) is reported: write sqrt(16).
  • Anything else is reported, with the reason. A word left over after a value (years = 20 years) is reported rather than ignored, because it usually hides a typo — put notes behind a value into a comment. A line that is clearly a calculation is never taken for a note: two known names in a row (a b + 1), let x = 5, var x = 5, x mod 3 and x y = 5 are reported with the fix.

A bracket or a function call may span several lines; draftply keeps reading until it closes:

1sum(150
2 100,
3 50
4)

Comments

// starts a comment, on its own line or behind a calculation:

1// Car costs 2026
2years = 20 // investment period20

A comment that is only a number (7 // 2) may be meant as whole-number division: it stays a comment but is underlined with the hint floor(7 / 2). x //= 3 is reported.

Ctrl + / (macOS ⌘ + /, German keyboard Ctrl + Shift + 7) comments out the selected lines or brings them back. On phones and tablets, long-press the selection and pick Comment out, or tap // in the key bar.

Headings

A line starting with # is a heading: not calculated, shown in its own colour, and a jump mark. The Headings button in the toolbar lists them; choosing one moves the cursor there. ## and ### are sub-headings.

1# Car costs
2## Fuel
3fuel = 15000 km * 6.5 L/100km * 1.80 €/L1,755 €
4## Insurance
5insurance = 800 €800 €

# is never a comment: behind code (x = 5 # note) it is reported. A heading also ends a total group.

Labels

Text before a : names the result:

1VAT: 0.19 * 100VAT:19
2"Area (m²)": 4.5 * 12Area (m²):54

A label may contain letters, digits, spaces and the usual punctuation — Area m², Net €, Item no. 1, Total (net) — but no operators. Quote it to be sure; the quotes are not shown. A time in a note (Dinner 19:30) is part of the note, not a label.

Text with a calculation in it

In a line of text, {…} is replaced by its result — units, lists and all:

1price = 1.85 €/L1.85 €/L
2liters = 40 L40 L
3Refuelling costs {price * liters} at {price}.Refuelling costs 74 € at 1.85 €/L.
4Rent {rent = 1200} plus {extra = 250} service chargesRent 1,200 plus 250 service charges
5rent + extra1,450

An assignment in braces counts like a line of its own. Ctrl + E (⌘ + E) inserts {} or wraps the selection in braces.

Details
  • {{ and }} are literal braces. Braces whose content cannot be calculated ({nme} with a typo) show {?} and the cause behind the line, as in a text in quotes.
  • A line holding nothing but {3 4} calculates like 3 4, so you can write the braces first and the sentence afterwards. An empty {} stays quiet.
  • Braces inside a label are shown as written, not replaced.
  • A text line is prose, not an item: total stops at it.

Variables and line references

Variables

name = value stores a value; later lines use it by name. A name starts with a letter or _, followed by letters, digits or _. Accented letters count, so names can be written in your language:

1Größe = 55
2Wohnfläche = 80 m80 m
3prêt = 100100
4prêt / 425

Upper and lower case differ (Größe ≠ größe). Not allowed in names: ×, ÷ and Greek letters (µ, Ω are unit symbols).

Compound assignment

+=, -=, =, /= update a variable: x += 1 is x = x + 1. They also work on list entries (costs[-1] += 100) and object fields (c.r = 2). The variable must already exist.

Every assignment shows the value it sets, including one into a list entry or a field: costs["Internet"] = 40 € shows 40 €.

Line references

@3 is the result of line 3:

15 * 315
2@1 + 1025

References follow their line: insert or delete lines above and the number is rewritten, like a spreadsheet formula (undo restores both). If the referenced line itself is deleted, the reference becomes @0 and reports reference to a deleted line — on purpose, so it cannot silently point at the wrong line.

An @ is a reference only on its own: in a text in quotes or behind a letter ("me@2x.de", john@3d.com, @3pm) it stays text.

Running total

A line with just total adds up the lines above it, back to the last blank line, heading, comment or earlier total. Free, and the quickest way to sum a list:

1# Groceries
212.5012.5
38.008
43.203.2
5Total: totalTotal:23.7

A name in front keeps the sum for later lines. A new item in the group counts on its own, there is no formula to extend:

1Rent: 1150 €Rent:1,150 €
2Power: 85 €Power:85 €
3Internet: 40 €Internet:40 €
4monthly = total1,275 €
5monthly * 1215,300 €

Units are kept and converted: 1 h above 30 min gives 1 h 30 min; the first line of the group picks the unit. A plain number below a unit takes that unit, so a 50 in a column of euros counts as 50 €. Dates and clock times cannot be added up: a group that holds one reports it — keep them in a block of their own. So does a group with a line that has an error, or with a bare list or matrix: total names the line instead of leaving it out of the sum. A list assigned to a name (prices = [10, 20]) is a definition and does not count.

Mixed quantities

Quantities that do not belong together get one sum each, as a list in order of appearance — an ordinary value, so @4[0] picks out the distance:

15 km5 km
21 h1 h
330 min30 min
4total[5 km, 1 h 30 min]

A plain number before the first unit has nothing to adopt yet and stays a figure of its own (1, 5 km → [1, 5 km]; 5 km, 1 → 6 km). Between two different quantities it also keeps its own place.

Numbers and arithmetic

OperatorMeaningExample
+ - * /the four basic operations; × and ÷ read as * and /7 / 2 → 3.5
^power2^8 → 256
% after a valuepercent200 * 15% → 30
% between valuesremainder (modulo)10 % 3 → 1
( )grouping; 3(x + 5) multiplies(1 + 2)(3 + 4) → 21
a..bwhole-number range, for for and plot1..5

-2^2 is -(2^2) → -4, as in mathematics and spreadsheets; write (-2)^2 for 4. A leading or trailing decimal point is fine: .5 + 5. → 5.5.

**No * needed behind a number.** A name or a function right after a number multiplies, as in maths — and as the CAS writes its results:

1x = 33
22x6
32x^2 + 119
41/2x0.1667
5r = 22
62pi * r12.5664
73sin(0)0

It binds tighter than and /: 1/2x is 1/(2x), and 6 / 2(1 + 2) is 1. Write x/2 or 1/2 x for half of x. A number with a unit is the exception: 1/2 km is half a kilometre (3/4 h is 45 min), a plain number divided by a number with a unit divides only the number. For the reciprocal write 1/(2 km). Powers still bind first: 2x^2 is 2 · x². Units come first: 2h stays two hours even when there is a variable h (likewise d g l m s t and the other unit symbols). The exception is a parameter of your own function or a loop variable: in f(t) = 2t + 1 the 2t is 2 times the parameter, so f(3) is 7. A variable with such a name (t = 2, then 9.81 t) does not win: the editor underlines the name at its assignment and suggests 9.81 t. Only one name follows the number: 2x y, not 2x y.

Thousands and millions. k and M directly behind a number: 25k → 25000, 1.5M → 1500000, 25k€ + 5k€ → 30000 €. Only when no other letters follow — 25kg stays 25 kilograms, and m is always the metre.

Decimal comma. With the comma as decimal sign (Settings → Number format), separate values with ;: sum(1,5; 2,5), [1,5; 2,5], also in named lists and function parameters. In period mode a comma is never a decimal sign. Comma and semicolon mixed in one list are reported. See Number format.

Calculations are exact wherever a finite answer exists — 0.1 + 0.2 == 0.3 holds. See Exact arithmetic.

Percentages

19% is 0.19 and shows as 19 %. Behind + or - a percentage is taken of the value on the left — that is how markups and discounts work:

ExpressionResultReads as
100 + 10%110100 plus 10 % of 100
250 - 15%212.5250 minus 15 % of 250
100 + 10% + 10%121two markups in a row
1200 * 19%22819 % of 1200
19% of 25047.519 % of 250
238 / 119%200net from gross
100 + (10% + 10%)120two percentages add up first

To show a plain number as a percentage, end the line with in % (to % and as % do the same): 15000 / 200000 in % → 7.5 %. The value stays 0.075, so 100 € + p after p = 15000 / 200000 in % adds 7.5 %.

A percentage keeps working as one when it is stored — in a variable or as a line you refer to with @:

1VAT = 19%19 %
2100 € + VAT119 €
3discount = 10%10 %
4100 € + VAT - discount107.1 €
5100 € * VAT19 €
6both = VAT + discount29 %
Details
  • % binds tighter than * and /, so 238 / 119% is 238 / 1.19.
  • A percentage divided by a number stays a percentage: 100 + 10% / 10 → 101. Multiplied, it becomes a plain number: 4 + 20% * 2 → 4.4.
  • A stored percentage behaves exactly like the written one: after p = 20%, 4 + p is 4.8, p + 5% is 25 %, p * 2 the plain number 0.4. Assigning anything else to p makes it a plain number again.
  • % between two values is the remainder (10 % 3 → 1). It takes the sign of the divisor, as in spreadsheets: -7 % 3 is 2, 7 % (-3) is -2. Before a minus % is always a percentage (50% - 10 → -9.5), hence the brackets around a negative divisor.

Comparison and logic

Truth values

A comparison answers true or false. In a calculation they count as 1 and 0, so sum([true, false, true]) → 2; any number other than 0 is true in if. Text is the exception: "true" == true is false.

true and false are constants like pi — true = 5 would overwrite them.

Comparison

OperatorMeaning
< > <= >=smaller, greater, or equal
== !=equal, not equal

Units are converted first: 2 m > 100 cm → true. Different quantities have no order — 5 km > 3 kg is reported — but they are never equal, so 5 km == 3 kg → false — always, whatever the numbers are (5 m == 5 s and 100 € == 100 $ are false). Two temperature scales (20 °C == 68 °F) are reported like <. Currencies compare as written, without exchange rates. Text compares with ==/!=, case-sensitively.

Comparisons chain as in mathematics: 1 < x < 10 means 1 < x and x < 10, so 3 > 2 > 1 → true.

and, or, not

x > 0 and x < 10, x < 0 or x > 10, not done. They accept any number (0 is false), so they also work with a form chip that writes 1 or 0. and and or stop as soon as the answer is clear, so x != 0 and 1/x > 2 also works for x = 0.

Conditional expression

value if condition else other picks one of two values, handy in one-line functions:

1discount(n) = 0.1 if n > 100 else 0function defined
2grade(p) = "A" if p >= 90 else "B" if p >= 80 else "C"function defined

There is no a ? b : c; draftply reports it and names this form.

Units

Writing quantities

Write the unit behind the number. Units convert and cancel on their own:

15 km + 300 m5.3 km
21500 W * 4 h6 kWh
31500 W * 4 h * 0.30 €/kWh1.8 €
4100 km / 2 h50 km/h
516 kWh/100km0.16 kWh/km
654 m^254 m²

A quantity can come in parts, each smaller than the one before:

13 h 20 min3 h 20 min
22 * 1 h 30 min3 h

+, - and comparisons need one quantity on both sides: 2 m + 3 kg and 5 € + 3 $ are reported. A plain number next to exactly one unit takes it: 5 km + 7 → 12 km. and / combine anything: 2 m 3 kg → 6 m·kg.

A unit written with / and no spaces belongs to its number, so dividing by a speed, a price or a density works as written:

1100 km / 70 km/h1 h 25 min 42.8571 s
21.8 € / 0.30 €/kWh6 kWh
330 L / 5 L/100km600 km

With spaces, / divides: 100 m / t divides by a variable t, while in 50 km/h the h is always the hour. A unit behind an exponent belongs to the whole power: 2^3 m → 8 m. Behind a bracket it belongs to the bracket: (pi / 2) rad, (2 + 3) km/h, [1, 2] m.

Built-in units

QuantityUnits
Lengthnm, µm (um), mm, cm, dm, m, km, in, ft, yd, mi
Massg, kg, t, oz, lb
Timens, µs (us), ms, s, min, h, d (day, days), wk (week, weeks)
Volumeml, dl, L (l); US: floz, pt, qt, gal
Energy · powerJ, kJ, Wh, kWh · W, kW, MW
Force · pressureN, kN · Pa, hPa, kPa, MPa, mbar, bar
FrequencyHz, kHz, MHz
Angledeg (°), rad
Currencies€, $, £, ¥, ₿, EUR, USD, GBP, CHF, JPY, CAD (never converted; EUR is €, USD $, GBP £, JPY ¥)
Temperature°C, °F, K — each only with itself, other scales via in

Anything else you can define with defunit.

Converting — in, to, convert

in, to or as behind a quantity converts it to another unit of the same kind:

15 km in mi3.1069 mi
236 km/h to m/s10 m/s
390 min in h1.5 h
42 days in h48 h
5[1 h, 30 min] in min[60 min, 30 min]

It applies to everything before it, so 2 km + 500 m in m is 2,500 m.

Temperatures convert the same way. They only calculate with their own scale (20 °C + 5 °C, an average of °C values); 20 °C + 68 °F is reported:

120 °C in °F68 °F
2300 K in °C26.85 °C

A temperature below absolute zero is reported (-300 °C, -1 K). °C and °F have no true zero, so multiplying, dividing or raising two of them and a percentage on top (100 °C + 10%) are reported; convert to K first. A plain number may scale them ((20 °C + 30 °C) / 2 → 25 °C). A temperature minus a temperature can be a temperature or a difference, and in another scale the two give different numbers — so converting it is reported; convert the temperatures first: (30 °C in °F) - (20 °C in °F) → 18 °F.

300K written without the space stays 300,000.

convert(value, "from", "to") does the same for a plain number, temperatures included. A value that already has a unit must be in the "from" unit, otherwise it is reported. value is the number, "from" the unit it is in and "to" the unit you want:

1convert(20, "C", "F")68
2convert(36, "km/h", "m/s")10 m/s
3convert(1, "mi", "km")1.6093 km
4convert(1, "gal", "L")3.7854 L
5convert(5, "km", "m") + 100 m5.1 km

The result carries the target unit and keeps calculating (temperatures come back as plain numbers). Currencies are not converted.

A unit defined with defunit takes precedence over a temperature letter: in a sheet that defines F as the farad (as the electronics library does), write °F for Fahrenheit.

Custom units

defunit adds units — for electronics, recipes, anything:

FormMeaning
defunit("V")a new base unit
defunit("Ohm", "V/A")a unit derived from others
defunit("kOhm", 1000, "Ohm")a scaled unit: 1 kOhm = 1000 Ohm

The parameters of defunit are:

  • "name": the name of the new unit, in quotes.
  • factor: how many of the base unit make one new unit, a plain number above 0. It comes before the base; without a factor, the base is taken as it is.
  • base: the unit it is measured in, in quotes, such as "V/A". Left out, the unit becomes a new base unit.
1defunit("V")1 V
2defunit("A")1 A
3defunit("Ohm", "V/A")1 Ohm
4470 Ohm * 0.01 A4.7 V
512 V / 4 A3 Ohm
610 mA * 2 kOhm20 V

The prefixes p n µ/u m k M G T work with your own units (10 mA, 5 kOhm). A unit may be a symbol (defunit("Ω", "V/A")), but then prefixes need their own defunit (defunit("kΩ", 1000, "Ω")). Built-in units cannot be redefined, and the factor must be a plain number greater than 0. Custom units belong to one document; put them in a library to share them.

How results are displayed

Results outside 0.1 … 1000 switch to a fitting prefix: 1500 m → 1.5 km, 0.008 m → 8 mm. Within a sheet, one quantity is shown in one unit, so 750 kg and 2252 kg both appear in tonnes rather than side by side in kg and t. This only changes the display — @n and further calculations keep full precision.

Fine print on units
  • Not rescaled: compound units (50 km/h), powers (20 m²), currencies, the target unit of convert, and a sheet that is consistently in a unit you chose (a sheet in cm stays in cm). Metric never switches to imperial.
  • Named results. Products land on the named unit of their dimension (5 N 3 m → 15 J). A fraction keeps its written form when it contains a named unit — 16 kWh/100km is a consumption, not 576 N — unless you named it yourself with defunit (V/A → Ohm) or spelled it from base units (2 kg 3 m / 4 s^2 → 1.5 N, 1 / 2 s → 0.5 Hz).
  • Same dimension multiplies to a power: 5 mm * 10 m → 0.05 m².
  • Powers and roots. 54 m^2 squares the unit, (54 m)^2 the whole quantity (→ 2916 m²). A root lowers the exponent if it stays whole: sqrt(9 m^2 / s^2) → 3 m/s; sqrt(4 m) is reported.
  • Unit names win after a number. You can call a variable km and still write 100 km; on its own, km is the variable.
  • Functions take plain numbers. exp, log, sqrt of a non-square unit and the like need a number without unit — exp(1 m) is reported, as is an exponent with a unit (2^(1 m)). sin, cos and tan take angles; atan2(1 m, 100 cm) converts both sides first.
  • Mixing plain numbers. Next to several units a plain number is ambiguous and reported (5 km + 300 m + 7); brackets form their own group (5 km - (300 m + 7) → 4.693 km).

Text

Text goes in double quotes. Joining, comparing and reading characters is free:

1name = "draft" + "ply"draftply
2"a" == "A" // case mattersfalse
3s = "Käse"Käse
4s[1] // counted from 0ä
5s[-1] // from the ende
6"Total: {3 * 4}"Total: 12
FunctionParametersResult
str(x)x: a number, list or datethe value as text, written as it is displayed — with the thousands separator if that is on (str(12345) → 12,345)
ord(text)text: one or more characters; the empty text is reportedthe code point of one character, or a list of code points for several (ord("Hi") → [72, 105])
char(code)code: a code point, a whole number, or a list of themthe character(s), the inverse of ord (char([72, 105]) → Hi)
print(x)x: any valueshows it in the result column (see Debug output)
len, sort, containssee the list functionslength, sorted list, search — list functions, so Pro

Escape sequences: \" quote, \\ backslash, \n line break, \t tab. Other backslashes stay as they are, so "C:\Users" reads fine.

Details
  • Single quotes are not text delimiters; 'hello' is reported.
  • Text cannot be changed in place (s[0] = "H" is reported); build a new one with +.
  • + joins text with text only: "Total: " + 5 is reported, write "Total: " + str(5) or "Total: {5}".
  • "5" == 5 is false; < and > do not apply to text.
  • len counts characters, so len("Käse") is 4. contains is case-sensitive. sort ignores case and places umlauts after z; a list must be all text or all numbers to be sorted.
  • for c in text: walks a string character by character (Pro, as all loops).

Dates and times

1d = date(2026, 6, 13)06/13/2026
2weekday(d) // Saturday6
3days(date(2026, 12, 31), d)201
4d + 30 // shown in your device's format07/13/2026
5addmonths(date(2026, 1, 31), 1)02/28/2026
6dformat(d, "dd.MM.yyyy")13.06.2026
FunctionParametersResult
date(y, m, d)y: year, 1–9999. m: month, 1–12. d: day, 1–31 (a date that does not exist is reported)a date
datetime(y, m, d, h, min[, s])as date, plus h: hour, 0–23. min: minute, 0–59. s (optional): second, 0–59a date with time
today · nownonetoday · the current date and time, to the minute
8:12nonea clock time today
day(d) · month(d) · year(d) · hour(d) · minute(d)d: a datethat part of it
weekday(d)d: a date1 = Monday … 7 = Sunday
days(d1, d2) or d1 - d2d1: the later date, d2: the earlier onedifference in days (with a time of day in fractions: 12 hours are 0.5)
addmonths(d, n) · addyears(d, n)d: a date. n: whole number of months or years, negative to go backthe shifted date, clamped to the month end (addmonths(date(2026, 1, 31), 1) → 02/28/2026)
monthstart(d) · monthend(d)d: a datefirst · last day of its month
dformat(d) · dformat(d, "pattern")d: a date. "pattern" (optional): yyyy year, yy two-digit year, MM month, dd day, HH hour, mm minute, ss second. Single letters stay text, so d.M.yyyy shows d.M.2026 — write dd.MM.yyyy. Text is quoted with '…', a doubled '' is an apostrophe ('it''s'), an unclosed quote is reported; doubled letters that are no field (MMM, DD, YYYY, hh) are reported; "" gives the device formatas text, in your device's format without a pattern

A date that does not exist is reported rather than rolled over to another day: date(2026, 2, 29), date(2026, 13, 1), datetime(2026, 1, 1, 25, 0). For the last day of a month there is monthend.

How dates are shown follows your device; Settings → Date format fixes one order.

Clock times and durations

Clock times are written as they are. A clock time minus another is a duration, a clock time plus a duration is a clock time again:

1start = 8:1208:12
2pause = 45 min45 min
3leave = start + 8 h + pause16:57
417:45 - start - pause8 h 48 min
517:45 > starttrue

Durations of an hour or more read as hours and minutes (8 h 48 min, also when typed in seconds: 3661 s is 1 h 1 min 1 s), shorter ones as minutes and seconds (2 min 30 s), days as 1 d 6 h; you can type them the same way. Seconds show whenever there are any (1 h 30 min 20 s), and a clock time with seconds shows them too (8:12 + 30 s → 08:12:30). Hours stay hours (45 h, not days). 90 min in h keeps the decimal 1.5 h. now - start is the time since start, and two dates without a time still give the number of days. Times count by the wall clock, as in spreadsheets: across a change to or from daylight saving time a day is still 24 h, and 1:00 + 2 h is 03:00.

Live

A sheet with now can run live: the clock icon next to such a line switches it on, and those lines recalculate at every full minute, so "When can I leave today?" counts down by itself. Live is saved with the file (live: true in the frontmatter) and pauses while the app is in the background. In the form view the same clock sits in the header. Free.

To keep the moment you started, the pin next to the clock replaces now in that line with the current time as text: start = now becomes start = datetime(2026, 10, 15, 8, 12), and now - start keeps counting from there. Ctrl+Shift+D (Cmd+Shift+D on Mac) does the same in the line of the cursor, or types the current time at the cursor if the line has no now.

Lists

[1, 2, 3] is a list. Writing lists, reading entries and calculating with them is free; the list functions below are Pro.

1prices = [4.5 €, 3 €, 8 €][4.5 €, 3 €, 8 €]
2prices[0] // counted from 04.5 €
3prices[-1] // from the end8 €
4prices * 2[9 €, 6 €, 16 €]
5prices[1] = 2.5 €2.5 €

Named lists

Keys make a list readable by name. Quotes are optional for one-word keys, and the list may span several lines:

1costs = [[Electricity: 120 €, Water: 80 €]
2 "Electricity": 120 €,
3 "Water": 80 €
4]
5costs["Water"]80 €
6costs["Internet"] = 40 € // adds an entry40 €
7costs[0] // by position works too120 €

keys(costs) and values(costs) return the keys and values; for v in costs: walks the values. Statistics such as sum and mean use the values in the order written.

A named list calculates like a list, name by name, with a number, a quantity or a second named list: a recipe for 4 becomes a recipe for 7 with one multiplication. Two named lists need the same names, in any order; different names are reported. A plain number next to quantities takes their unit, as in 5 km + 7.

1recipe = ["Flour": 200 g, "Butter": 125 g, "Milk": 200 ml][Flour: 200 g, Butter: 125 g, Milk: 200 ml]
2recipe * 7 / 4[Flour: 350 g, Butter: 218.75 g, Milk: 350 ml]
3["a": 1 €, "b": 2 €] + ["b": 20 €, "a": 10 €][a: 11 €, b: 22 €]
4["a": 5 km, "b": 300 m] + 7[a: 12 km, b: 307 m]

A list of one entry is the entry itself: [5] is 5, so len([5]) is 1 and [5][0] is 5. A percentage in a list is its factor: [10%, 20%] holds 0.1 and 0.2, and a unit next to it ([1 m, 1 %]) is reported.

Lists and named lists are values, as in a spreadsheet: after budget = costs, changing budget["Water"] leaves costs as it was, and a function that changes a list it was given changes its own copy — return it to keep the change. Objects of a class are shared instead, so a method can change its own fields.

List functions Pro

FunctionParametersResult
len(list)list: a list or a textnumber of entries (characters, for text)
range(end) · range(start, end[, step])end: first value not included. start: first value, default 0. step (optional, default 1): the distance; may be negative or fractional, not 00 … end−1 · start … end−1
linspace(from, to, n)from, to: first and last value. n: how many values, a whole number of 1 or more (at most 100,000, like range)n evenly spaced values, both ends included
first(list) · last(list)list: a non-empty listfirst · last entry
push(list, x) · pop(list)list: the list. x: the value to add at the endwith x appended · without the last entry
sort(list) · reverse(list)list: numbers, or texts. sort needs all numbers or all textssorted ascending · reversed
contains(list, x)list: a list or a text. x: the value to look for (in a text: a part of it)true if x is in the list (or part of a text)
cumsum(list) · cumprod(list)list: numbersrunning sum · running product
list(a, b, …) · append(list, x)list: any values, kept as they are. x: added as one entry, even when it is a list (append([1, 2], [3, 4]) → [1, 2, [3, 4]])a list of any values · the list with x appended
keys(d) · values(d)d: a named listits keys · its values
map(f, list) · filter(f, list)f: the name of a function, without brackets (square, not square(x)), that takes one entry. list: the list to work onf applied to each entry · entries for which f is true
1range(0, 10, 2)[0, 2, 4, 6, 8]
2range(0, 1, 0.25)[0, 0.25, 0.5, 0.75]
3linspace(0, 10, 5)[0, 2.5, 5, 7.5, 10]
4cumsum([1, 2, 3])[1, 3, 6]
5square(x) = x^2function defined
6map(square, [1, 2, 3])[1, 4, 9]
7even(n) = n % 2 == 0function defined
8filter(even, [1, 2, 3, 4])[2, 4]

These return new lists; the original stays unchanged. map and filter take a function name — a built-in or your own.

Lists of anything

list() and append collect whole vectors, texts or named lists without flattening them — the way to gather results in a loop:

1rows = list()[]
2for i in 1..3:
3 rows = append(rows, [i, i*2])
4rows[[1, 2], [2, 4], [3, 6]]
5rows[1][2, 4]

A bracket list of non-numbers ([v1, v2] with vectors, ["a", "b"]) is such a list too. sum and mean add up every number inside.

Lists with units
  • If every entry has a unit, the list keeps it, converted to the unit of the first entry: [1 m, 2 km] → [1 m, 2000 m], sum([1 m, 200 cm]) → 3 m.
  • A plain number among them takes that unit: [5 km, 7] → [5 km, 7 km]. Next to several units it is reported ([1 m, 2 km, 3]).
  • Different quantities in one list ([1 m, 2 kg]) are reported.
  • Statistics that change the quantity change the unit with it: the variance of metres is in m² (variance([1 m, 2 m, 3 m]) → 1 m²), the covariance of metres and seconds in m·s.

Matrices

Inside brackets, , or ; separate entries and ;; starts a new row:

1A = [1, 2 ;; 3, 4] // 2×2 matrix[[1, 2], [3, 4]]
2c = [1 ;; 2 ;; 3] // column vector[[1], [2], [3]]
3A * 2[[2, 4], [6, 8]]
4A[1, 0] // row 1, column 03
5A[-1, -1] // last entry4
6A[0, 1] = 55

Entries count from 0; a single index counts row by row (A[2] → 3). In decimal-comma mode write A[1; 0]. Tall results can be collapsed with the chevron next to the line number; on phones they start collapsed.

Matrix functions Pro

FunctionParametersResult
det(A) · inv(A)A: a square matrix. inv is reported for a singular onedeterminant · inverse
transpose(A) · trace(A)A: a matrixrows and columns swapped · sum of the diagonal
norm(A)A: a matrixFrobenius norm: the square root of the sum of all squared entries
eye(n) · zeros(r, c) · ones(r, c)n: size of a square matrix. r, c: number of rows and columns; zeros(n) is n × nidentity · all zeros · all ones
size(A) · size(A, dim)A: a matrix. dim (optional): 1 for rows, 2 for columns[rows, cols] · the rows or the columns
1det([1, 2 ;; 3, 4])-2
2inv([1, 2 ;; 3, 4])[[-2, 1], [1.5, -0.5]]

A list of equal rows works too (det([[1, 2], [3, 4]])). A list or matrix times a quantity multiplies every entry ([1, 2] * 3 m → [3 m, 6 m]).

Matrices keep the unit of their entries — det of a 2×2 matrix in m is in m², inv in 1/m — and may hold complex numbers — enough for AC mesh analysis. A^n needs a whole-number n of -1 or more (A^-1 is the inverse, a large n is quick); a result too large for a number is reported. An empty matrix [] has no det, inv, trace, norm or size. Whether a matrix of approximate values (sqrt(2), pi) is singular is judged by its rounding error, not by a fixed threshold: det then shows 0 and inv reports it, while a tiny but regular matrix still inverts.

Exact arithmetic

draftply calculates with exact fractions wherever a finite answer exists — for + - * /, whole powers, %, rounding, units, total, lists, matrices, statistics and most finance functions, however long the number:

10.1 + 0.2 == 0.3true
21 / 3 * 31
3round(1.005, 2)1.01
4123456789 * 987654321 + 333333333121,932,631,445,968,602

Only the display rounds, to the decimal places you set. Roots, pi, e, logarithms and trigonometry have no finite form; they use about 16 digits, and sqrt(2)^2 == 2 still holds because such values count as equal at 12 significant digits.

Limits and display
  • Whole numbers stay exact up to about 9,800 digits, fractions up to about 1,200; beyond that draftply continues with 16 digits. Matrices with more than 10,000 entries also use 16 digits.
  • Exact results beyond ±1.8·10³⁰⁸ stay exact (10^400 → 1e+400). A function whose approximate result overflows reports it as too large (exp(1000)), and approximate results below 2.2·10⁻³⁰⁸ as too small (exp(-1000)); exact values such as 1e-400 stay. A fraction too long to stay exact that falls below 2.2·10⁻³⁰⁸ is reported as too small too (1/(10^1300)).
  • Approximations show at most 15 significant digits, from 10¹⁵ on in exponent form; whole numbers are shown in full up to 40 digits, then in exponent form (1.2346e+45).
  • floor, ceil and round treat an approximation that is only a few units of its last digit away from a whole number as that number: floor(log10(1000)) → 3.
  • A non-zero number is never shown as 0: below your decimal places it shows its first significant digit, below 0.0001 in exponent form (6.626e-34).
  • + and - drop a rest below 14 significant digits of their operands as rounding noise, so sqrt(2)^2 - 2 is 0 — and a == b exactly when a - b == 0. Tiny values are compared relative to their size.
  • Exact: sqrt of a perfect square (sqrt(2.25) → 1.5), cbrt of a cube, log10/log2 of an exact power (log10(1000) → 3), sin, cos and tan in degrees where the value is rational (sin(30°)), gcd, lcm, factorial, nCr, convert, number-system and bit functions, sum, mean, median, variance, cov, percentile, cumsum, and pv, fv, pmt, npv with whole periods. Approximate: std, corr, rate, irr, nper.

Functions

Every function is called as name(a, b). Arguments are separated by commas and taken in the order of the parameter list. Brackets mark optional parameters, as in round(x[, digits]): leave them out and the default in the table applies.

Math functions

FunctionParametersResult
sqrt(x) · cbrt(x)x: any number. A negative x gives an imaginary result for sqrt; cbrt stays realsquare root · cube root (cbrt(-8) → -2)
abs(x) · sign(x)x: any number. abs keeps a unit; sign always gives a plain numberabsolute value · −1, 0 or 1
exp(x) · pow(x, n)exp: x is the exponent, a plain number. pow: x is the base, n the exponenteˣ · xⁿ (same as x^n, pow(2, 10) → 1,024)
log(x) · log10(x) · log2(x)x: a positive number, exactly one argument. There is no base argument; for another base write log(x) / log(b)logarithm to base e · 10 · 2; ln(x) is the same as log(x)
round(x[, digits])x: the number. digits (optional, default 0): decimal places, a whole number; negative values round to tens, hundreds … (round(1234.5, -2) → 1,200). Halves round away from zero (round(-2.5) → -3)rounded to digits places; a percentage keeps its % sign (round(33.333%, 2) → 33.33 %)
floor(x[, digits]) · ceil(x[, digits])the same as round: x, then the optional digits. floor always rounds down, ceil always uprounded down · rounded up
min(a, b, …) · max(a, b, …)two or more values, or one list (min([3, 1, 2])). All must be the same kind of quantity: min(5 km, 3 kg) is reported. Dates and clock times work too, as in < (max(date(2026, 1, 1), date(2026, 6, 1))); sort orders themsmallest · largest value, with its unit
clamp(x, lo, hi)x: the value to limit. lo and hi: the lower and upper bound; lo must not exceed hix limited to lo … hi
mod(a, b)a: the dividend. b: the divisor, not 0. The result has the sign of b: mod(7, -3) → -2remainder, same as a % b
hypot(x, y)x and y: the two legs of a right triangle, exactly two valuessqrt(x² + y²) (hypot(3, 4) → 5)
lerp(a, b, t)a: start value. b: end value. t: position between them: 0 gives a, 1 gives b, 0.5 the middle. Values of t outside 0 … 1 continue the line (lerp(10, 20, 1.5) → 25)a + t·(b − a)
proportion(a, b, c)rule of three: a is to b as the unknown is to c, that is a : b = ? : ca · c / b (proportion(3, 4, 8) → 6)
1round(3.14159, 2)3.14
2round(3.14159 m, 2)3.14 m
3min(5 km, 300 m)300 m
4clamp(15, 0, 10)10
5hypot(3, 4)5
6proportion(3, 4, 8)6

Units are kept; min and max compare different units of one quantity and report different quantities (min(5 km, 3 kg)). Constants: pi, e, inf.

Where a function counts, it needs whole numbers — gcd, lcm, factorial, nCr, the digits of round, randi, hex and the bit functions: gcd(2.5, 5) is reported rather than rounded.

Trigonometry

A plain number is an angle in radians. With a unit, sin, cos and tan take degrees too: sin(30°), sin(30 deg). The inverse functions return radians; atan(1) in deg gives 45° (a plain number counts as radians there too). In degrees the values are exact where they are rational — sin(30°) is exactly 0.5, tan(45°) exactly 1 — and accurate right up to the poles; tan(90°) is reported, as is atan2(0, 0) (the point (0, 0) has no angle).

FunctionParametersResult
sin, cos, tanx: an angle. A plain number is in radians; an angle with unit (30°, 30 deg, 1 rad) is taken as it issine, cosine, tangent
asin, acos, atanx: a plain number. asin and acos take values from −1 to 1; beyond that the result is complexinverse functions, in radians
atan2(y, x)y, then x: the coordinates of a point. Both signs decide the quadrant: atan2(0, -1) → 3.1416 (π)angle of the point (x, y), in radians
sinh, cosh, tanh, asinh, acosh, atanhx: a plain number. acosh needs x ≥ 1 and atanh needs −1 < x < 1 for a real resulthyperbolic functions; outside those ranges the result is complex, like asin(2)
deg2rad(x) · rad2deg(x)x: a plain number of degrees resp. radians (deg2rad(180) is π, about 3.1416); an angle with unit is reported heredegrees → radians · radians → degrees
1sin(pi / 2)1
2sin(30°)0.5
3cos(180 deg)-1
4atan2(1, 1) // π/40.7854
5rad2deg(pi)180
6cos²(pi / 3)0.25

A power right after the function name applies to the result, as in maths: cos²(x) and cos^n(x) are cos(x)^2 and cos(x)^n (for sin, cos, tan and the hyperbolic functions). cos^-1(x) is not accepted — write acos(x) for the inverse or 1 / cos(x) for the reciprocal.

Combinatorics

FunctionParametersResult
factorial(n)n: a whole number, 0 or more (factorial(2.5) is reported)n! (factorial(0) → 1)
gcd(a, b, …) · lcm(a, b, …)two or more whole numbersgreatest common divisor · least common multiple
nCr(n, k)n: how many items there are to choose from. k: how many are picked. Both whole numbers, 0 or morecombinations — order does not matter; 0 when k > n
nPr(n, k)n and k as in nCr: n items, k of them pickedpermutations — order matters; 0 when k > n
1factorial(5)120
2nCr(49, 6)13,983,816

Randomness

FunctionParametersResult
rand()nonedecimal in [0, 1)
rand(min, max[, digits])min and max: the range, min not above max. digits (optional): decimal places of the result, 0 to 15; without it the value is not roundeddecimal in [min, max), rounded to digits places if given
randi(min, max)min and max: whole numbers (below 2⁵³), both ends possiblea whole number, both ends included
1randi(1, 6) // a die6
2rand(1, 10, 1)7.2

A value stays put while you edit elsewhere. It is drawn again when you edit its own line, reopen the file, or tap the dice — next to the result in the editor, or in the form view for x = rand(…)/x = randi(…).

Statistics Pro

Each function takes a list or several values: mean([2, 4, 6]) or mean(2, 4, 6).

FunctionParametersResult
sum · mean · median · modea list, or the values written one after another (mean(2, 4, 6)); never both at oncesum · average · middle value · most frequent value (a tie is reported)
std · variancea list, or values as abovesample standard deviation · variance (÷ n−1); both calculated exactly from the entries, so a large offset (std([1e15+1, 1e15+2, 1e15+3]) → 1) costs no digits
mada list, or values as abovemedian absolute deviation: the median of the distances from the median
percentile(list, p)list: the data. p: a percentage from 0 to 100; percentile(data, 50) is the median, and percentile(data, 90%) the 90th percentilethe value at p %, interpolated between entries
quantile(list, q)list: the data. q: a fraction from 0 to 1; quantile(data, 0.5) is the medianthe value at q, interpolated between entries
corr(xs, ys) · cov(xs, ys)xs, ys: two lists of equal length; pairs are formed by positionPearson correlation · sample covariance
linreg(xs, ys)xs, ys: x and y values, as in corrstraight-line fit: named list with slope, intercept and r2

A named list or a list() with units takes part like a plain list: the first entry sets the unit, the others are converted, and quantities that do not belong together are reported (sum(["walk": 500 m, "drive": 12 km]) → 12.5 km).

1mean(2, 4, 6)4
2median([1, 3, 5, 7])4
3percentile([1, 2, 3, 4, 5], 75)4
4r = linreg([1, 2, 3, 4], [2.1, 3.9, 6.2, 7.8])[slope: 1.94, intercept: 0.15, r2: 0.9957]
5r["slope"]1.94

Finance Pro

Signs as in spreadsheets: money you receive is positive, money you pay is negative. Rates are per period, as decimals (0.05 / 12 for 5 % a year paid monthly).

FunctionParametersResult
pmt(rate, nper, pv[, fv])rate: interest per period as a decimal. nper: number of periods. pv: the amount borrowed (positive) or invested. fv (optional, default 0): the balance wanted after the last periodpayment per period, negative when you pay it
pv(rate, nper, pmt[, fv])rate and nper as above. pmt: payment per period, negative when you pay. fv (optional, default 0): the balance after the last periodpresent value: what the payments are worth today
fv(rate, nper, pmt[, pv])rate, nper and pmt as above. pv (optional, default 0): the amount at the startfuture value of the payments and the start amount
nper(rate, pmt, pv[, fv])rate, pmt, pv, fv as abovenumber of periods (reported if no positive number reaches the goal)
rate(nper, pmt, pv[, fv])nper, pmt, pv, fv as aboveinterest rate per period
npv(rate, cashflows)rate: discount rate per period. cashflows: a list of payments; the first entry is at time 0 and not discounted, each next one comes a period laternet present value
irr(cashflows)cashflows: a list as in npvinternal rate of return: the rate at which npv is 0
ipmt(rate, per, nper, pv[, fv])rate, nper, pv and fv as in pmt. per: the payment to look at, a whole number from 1 to nperthe interest part of payment number per, negative when you pay it
ppmt(rate, per, nper, pv[, fv])the same as ipmtthe repayment part of that payment; ipmt plus ppmt is pmt
cagr(start, end, years)start: the value at the beginning. end: the value at the end, with the same sign. years: how long it took, greater than 0growth rate per year, (end / start)^(1 / years) − 1
effrate(nominal, npery)nominal: the yearly rate as quoted, as a decimal. npery: how often a year the interest is added, a whole number from 1effective yearly rate, (1 + nominal / npery)^npery − 1
nomrate(effect, npery)effect: the effective yearly rate. npery as in effratethe yearly rate as quoted that gives effect

Amounts may carry a currency or unit: pmt, ipmt, ppmt, pv, fv and npv return it (pmt(5% / 12, 240, 200000 €) is in €), different currencies in one call are reported, and rate, nper, irr, cagr, effrate and nomrate stay plain numbers (cagr takes two amounts in the same currency or unit). Rates of −100 % or less are reported. When the cash flows change sign more than once, several rates can fit — rate and irr then name them instead of picking one (irr([-100, 230, -132]): 10 % and 20 %).

1pmt(0.05 / 12, 240, 200000) // loan: monthly rate-1,319.9115
2fv(0.04 / 12, 120, -200) // saving 200 a month29,449.9609
3npv(0.08, [-1000, 400, 400, 400]) // today, then 3 years30.8388
4irr([-1000, 400, 400, 400]) // ≈ 9.7 %0.097
5ipmt(0.05 / 12, 1, 240, 200000) // interest in the 1st payment-833.3333
6ppmt(0.05 / 12, 1, 240, 200000) // repayment in the 1st payment-486.5781
7cagr(100, 150, 3) // 100 became 150 in 3 years0.1447
8effrate(0.12, 12) // 12 % a year, added monthly0.1268

ipmt and ppmt split one payment, so they follow the signs of pmt; the interest part is the rate times what is still owed, so it shrinks and the repayment grows from payment to payment:

1rate = 5% / 120.4167 %
2loan = 10000 €10,000 €
3ppmt(rate, 1, 12, loan)-814.4082 €
4ppmt(rate, 12, 12, loan)-852.5226 €
5ipmt(rate, 1, 12, loan)-41.6667 €
6ipmt(rate, 12, 12, loan)-3.5522 €

cagr gives a rate as a decimal, 0.1447 is 14.47 % a year; both values must be positive, or both negative. effrate and nomrate convert between the two ways of quoting a yearly rate, and nomrate(effrate(r, k), k) is r again. per outside 1 … nper, a fractional per or npery, and a rate of −100 % or less are reported.

Number systems and bits

0x, 0b and 0o write hexadecimal, binary and octal numbers; they mix with everything else.

FunctionParametersResult
hex(x) · bin(x) · oct(x)x: a whole number. A negative number keeps its minus sign (hex(-1) → -0x1)x as text in base 16 · 2 · 8
dec("0xFF")"value": text with a 0x, 0b or 0o prefix (dec("ff") and a plain number are reported)the number that the text stands for
band(a, b) · bor(a, b) · bxor(a, b)a, b: two whole numbersbitwise AND · OR · XOR
bnot(a)a: a whole numberbitwise NOT (bnot(5) → -6)
shl(a, n) · shr(a, n)a: a whole number. n: number of bits to shift, a whole number from 0 upa shifted left · right by n bits
10xFF + 1256
2hex(255)0xFF
3bin(10)0b1010
4hex(band(0xF0, 0x3C))0x30
5shl(1, 70)1,180,591,620,717,411,303,424

Integers of any size; negative numbers act as two's complement (band(-1, 255) → 255). ^ is the power, so XOR is bxor.

Complex numbers

A number with i attached is imaginary: 3i, 0.5i. A plain i stays a normal variable name.

1sqrt(-1)1i
2(2 + 3i) * (1 - 1i)5 + 1i
3exp(1i * pi)-1
4log(-1)3.1416i
FunctionParametersResult
re(z) · im(z)z: a complex or a plain numberreal part · imaginary part
abs(z) · arg(z)z: a complex or a plain numbermagnitude · angle in radians
conj(z)z: a complex numberconjugate: 2 + 3i → 2 - 3i
roots(x, n)x: the number, may be complex. n: the degree, a whole number of 1 or moreall n solutions of z^n = x, real ones first

sqrt, exp, log, log10, log2, sin, cos, tan, asin, acos, atan and ^ accept complex values and return the principal value, the usual convention in maths software. One exception: an odd root of a negative number is the real one.

1(-8)^(1/3)-2
2(-4)^(1/2)2i
3roots(-8, 3)[-2, 1 + 1.7321i, 1 - 1.7321i]

Complex numbers also work with units, in lists (sum, mean) and matrices, and solve returns complex roots:

1defunit("V")1 V
2defunit("A")1 A
3defunit("Ohm", "V/A")1 Ohm
4Z = 50 + 30i Ohm50 + 30i Ohm
5Z * 2 A100 + 60i V
6abs(Z)58.3095 Ohm
Limits
  • No order: 1i < 2 is reported, and so are sort, min, max, median, percentile, quantile on complex lists — compare abs(z) instead. == and != work. std, variance, mode, corr, cov and linreg take real numbers only.
  • Not as input to solve, simplify, factor, diff, integrate.
  • With a unit, both parts scale together (50000 + 30000i Ohm → 50 + 30i kOhm). sqrt(50 + 30i Ohm) is reported, as it would need Ohm^0.5.

Decibels and E-series

FunctionParametersResult
db(ratio) · db2ratio(db)ratio: a power ratio above 0 (db(0) is undefined). db: a level in decibelspower ratio → dB (10·log10) · and back
dbm(watts) · dbm2w(dbm)watts: a power in watts, above 0. dbm: a level in dBmwatts → dBm (relative to 1 mW) · and back
e12(x) · e24(x)x: a positive numbernearest standard value of the E12 · E24 series, by ratio (e12(2) → 2.2)
1db(2)3.0103
2dbm(1)30
3e12(1300)1,200
4e12(96)100

For voltage or current ratios (×20), square the ratio: db(u_ratio^2).

Pro tools

Charts Pro

A chart takes the place of the result; each has buttons to export it as SVG, PNG or CSV.

CallDraws
plot(expr[, from..to])curves of an expression in x
scatter(xs, ys)points
bar(values[, xs])bars
pie(values[, labels]) or pie(named list)slices
hist(data[, bins])a frequency histogram
surface(expr[, from..to])a 3D surface of an expression in x and y
scatter3(xs, ys, zs)points in 3D
1plot(sin(x), cos(x), 0..2*pi)chart
2plot(x^2, -5..5, "Parabola", "x", "y")chart
3plot(exp(x), 1..20, "logy")chart
4bar(["Actual": [10, 20, 30], "Planned": [12, 18, 35]], [2024, 2025, 2026])chart
5pie(["Rent": 900 €, "Food": 320 €, "Transport": 140 €])chart
6surface(sin(x) * cos(y), -3..3)chart

Text arguments are, in order, the title and the axis labels; "logx", "logy" and "loglog" switch to logarithmic axes. A label in front of the call (Revenue: bar(…)) also becomes the title.

Several series: give plot several expressions, or pass a named list to plot, scatter or bar — its keys become the legend.

All parameters
ChartParametersDefaults
plotexpr…, from..to, yFrom..yTo, steps, "title", "x", "y", "logy"x −2π..2π, 300 steps (2–10,000)
scatterxs, ys (equal length) or xs, [named lists], then labels—
barvalues or named list, optional xs, then labelsbars numbered 0, 1, 2 …
pievalues, labels or a named list, "title"slices numbered 1, 2, 3 …
histdata, bins, then labels10 bins (2–200)
surfaceexpr, xFrom..xTo, yFrom..yTo, steps, then 4 labels−2π..2π, y like x, 30 steps (2–200)
scatter3xs, ys, zs, then 4 labels—
  • A logarithmic axis skips values ≤ 0; tooltips and exports keep the real values.
  • pie shows a shared unit in its legend; negative values are reported.
  • 3D charts: drag to rotate, double-click to reset; exports use the angle on screen. A surface leaves holes where the expression has no value.
  • CSV export: one column per curve; x,y,z for 3D; label,value for pie.

Algebra (CAS) Pro

FunctionParametersResult
solve(expr, x)expr: an expression that equals 0, or an equation such as 2x + 3 = 7; polynomials up to degree 2. x: the unknown to solve forsolutions of expr = 0 (or of the equation)
solve(eq1, eq2, …, x, y, …)one equation per unknown, then the unknowns in the same order; the equations are solved exactly, with the coefficients as writtenthe value of each unknown
simplify(expr)expr: a polynomial in your variablesexpanded and combined
factor(expr)expr: a polynomial of degree 2 or lessfactored
1solve(x^2 - 4, x)x = -2, x = 2
2solve(x + y == 3, x - y == 1, x, y)x = 2, y = 1
3solve(x^2 + 1, x)x = -1i, x = 1i
4solve(6/(x + 1) == x, x)x = -3, x = 2
5simplify((x + 1)^2)x² + 2x + 1
6factor(x^2 - 4)(x − 2)(x + 2)

Variables from the sheet. The unknown stays unknown even if a line above assigns it; every other variable is filled in — that is how solve works with values from a form:

1x = 33
2a = 22
3solve(a*x - 8, x)x = 4

simplify and factor name no unknown, so they keep all your variables symbolic.

Numbers in the result follow your number format: decimal comma in comma mode, thousands separators and the sheet's decimal places for solutions (x = 12,345.678; two solutions that would look alike get more places). Coefficients in an expression have no thousands separators. The discriminant of a quadratic is calculated exactly from the coefficients as written, so roots that lie close together stay two roots (x^2 - 2x + 1 - 1/10^12).

Details
  • Systems must be linear once denominators are multiplied out.
  • A solution that makes a denominator zero is dropped: solve(x/x == 1, x) → true for every x except 0.
  • factor stays in the real numbers: factor(x^2 + 1) is irreducible over the reals.
  • simplify does not cancel x^2 / x or apply trigonometric identities; those are reported. Powers of an unknown need whole exponents from 0 to 8 (x^2.5 is reported); constants may have any exponent (2^0.5 * x).
  • Complex numbers in the input are not supported.

Calculus Pro

CallParametersResult
diff(expr, x)expr: the expression. x: the variable to differentiate bythe derivative, symbolically
diff(expr, x, point)point: the value of x where the slope is wantedthe derivative at that point
integrate(expr, x)expr: the expression. x: the variable to integrate bythe indefinite integral, symbolically (with + C)
integrate(expr, x, a, b)a, b: lower and upper limit of x; inf is allowed as a limitthe definite integral, numerically (steps such as floor(x) cost the last digits)
1diff(x^3, x)3x²
2diff(x^2, x, 3)6
3integrate(sin(x), x)−cos(x) + C
4integrate(x^2, x, 0, 3)9

As with solve, x stays the variable even if the sheet assigns it.

diff at a point evaluates the symbolic derivative there — exact where the point is (diff(x^2, x, 1e10) → 20000000000) — and your own one-line functions are differentiated through their formula. Where there is no derivative (diff(abs(x), x, 0), diff(1/x, x, 0)), it says so.

integrate copes with singularities at the limits, with infinite limits and with large limits such as 1e10, and keeps units (integrate(2 N, x, 0 m, 3 m) is 6 J). It reports integrals that diverge or oscillate faster than it can resolve (integrate(sin(x), x, 0, 1e6), integrate(sin(1000000*x), x, 0, 1)), and an integrand that is not real somewhere between the limits (integrate(sqrt(x), x, -1, 1)), and shows only the digits it has computed reliably:

1integrate(1/sqrt(x), x, 0, 1)2
2integrate(exp(-x), x, 0, inf)1

If the integrand has a pole between a and b (integrate(1/x, x, -1, 2)) or the integral diverges (integrate(x^2, x, 0, inf)), the integral does not exist and integrate says so instead of returning a number.

Your own functions Pro

A one-line function:

1f(x) = x^2 + 2*xfunction defined
2f(3)15
3discount(n) = 0.1 if n > 100 else 0function defined

A longer one with def, indented below:

1def dot(a, b, scale = 1):function defined
2 s = 0
3 for i in 0..len(a) - 1:
4 s = s + a[i] * b[i]
5 return s * scale
6
7dot([1, 2, 3], [4, 5, 6])32

Parameters may be numbers, lists or text; parameters with a default are optional, and a default may use the parameters before it (def g(a, b = a 2):). A function may call itself (up to 100 levels deep). Inside the body a parameter is a variable even when it is also a unit: f(t) = 2t + 1 gives f(3) → 7. A function with a return that a call does not reach reports returned no value, and so does one without any return that only calculates locally (def f(x): with y = x 2 and nothing else); a function that prints or changes an object or a list entry may end without a value. A def without an indented body is reported.

A function with the name of a built-in replaces it for the whole document, and its line says so — the document keeps calculating the same way even if a later draftply adds a built-in of that name. These names are part of the language and cannot be used: plot, scatter, bar, hist, pie, surface, scatter3, diff, integrate, csv, csvwrite, map, filter, defunit.

Loops and conditions Pro

Blocks are marked by indentation:

1total = 00
2for i in 1..10:
3 if i == 5:
4 break
5 if i % 2 == 0:
6 continue
7 total = total + i
8
9x = 100100
10while x > 1:
11 x = x / 2
12
13t = 2525
14if t < 0:
15 state = "ice"
16elif t < 100:
17 state = "water"
18else:
19 state = "steam"

for walks a range, a list, the values of a named list (for k in keys(d): for the keys) or the characters of a text. At most 10,000 passes per loop. return value leaves a function.

Debug output

Turn on View → Debug output to see what a loop does:

1total = 0
2for i in 1..3: // i=1: total=1
3 total = total + i // i=2: total=3
4 print(total) // ↳ 1, 3, 6

Next to each for/while a trace line shows the loop variable and every value that changed in that pass (passes without changes are left out; inner loops trace at their own for, function calls at the call). print(x) lists every value it printed on its own line, up to 50.

The trace runs down the result column beside the loop's indented lines and only takes extra room below the loop, where it is longer than the loop itself. In the compact view the result strip widens for it, up to its usual limit.

Classes Pro

1class Circle(r):function defined
2 def area():
3 return pi * self.r^2
4
5c = Circle(5)Circle(r: 5)
6c.area()78.5398
7c.r *= 210

The parameters of the class header become fields, read inside methods as self.name.

Form view Pro

The form view turns a sheet into a fillable form: input fields on top, results below, plus charts and CSV buttons. The sheet itself is unchanged — the form only reads and writes the variables you link.

Form → Edit form… opens the editor; Form → Form view switches between sheet and form. Automatic proposes fields: every variable set to a plain value becomes an input, everything that depends on them an output. Apply all or tick single fields.

FieldWhat it is
Number inputedits a value (capital = 1000 €); an optional unit is shown and kept
Text inputedits text (loc = "JN48HU") without the quotes; the " button turns it on
Choicechips such as Yes = 1 / No = 0
Datea calendar picker, automatically for a variable holding a date
Diceredraws a rand(…)/randi(…) variable
Outputa result, read-only but copyable
Sectiona heading that groups the fields below; tap to fold, ⌄ to start folded
Charts · CSVpick which charts appear (size S–XL) · label or hide CSV buttons

Fields arrange in up to three columns as the window allows. Arrows move rows up and down.

How a form is stored

The form travels inside the .dply file, in its header:

1---
2form_title: Compound interest
3form_description: Quick estimate
4form_inputs: #Basics, Capital|kapital{€}, #Assumptions|collapsed, Reinvest?|reinvest[Yes:1,No:0], Locator|loc1"
5form_outputs: Net gain (€)|net_gain
6form_plots: 1
7form_plot_size: l
8---

label|variable, {unit}, " for text, [label:value,…] for chips, # for a section, |collapsed to start it folded. form_plot_size is s, m (default), l or xl. An emptied number field restores its last value rather than deleting the assignment.

Offer export Pro

A sheet with labeled lines and a total already is a quote. File → Offer → Export offer turns it into a PDF with your letterhead; New offer starts a sheet shaped like one; Export offer as CSV writes just the item table.

1Dear Mrs Meier,
2
3please find our quote for the bathroom below:
4
5Tiling: 8 h * 45 €/hTiling:360 €
6Material: 320 €Material:320 €
7Subtotal: totalSubtotal:680 €
8Discount: -1 * @7 * 10%Discount:-68 €
9Total: @7 + @8Total:612 €
10
11Best regards,
12Max Mustermann
  • Text lines become paragraphs, labeled lines rows of the table, the last row of a table with a total the bold sum. A blank or text line ends the table.
  • Left out: unlabeled calculations, comments, charts. If any line has an error, export asks first.
  • Amounts print with two decimals — with a currency or without (Rent: 1200 prints 1,200.00), rounded to the cent like the prices with a currency. Items with parts of a cent are reported before export, with a button Round to cents and export that rounds those items for the export (printed rows then add up to the printed total). Afterwards Apply to draft writes the rounding into your sheet: each line becomes round(…, 2) and keeps its original expression in a // before: comment; Undo brings the old text back.
  • One currency: items in different currencies (€ and $) block the export; the message names the items and their currencies.
  • Before sending: the first export asks once to confirm that you check amounts, tax rate and details before the offer goes out; the Offer menu keeps a short reminder. Keep the exported PDF: it is what your customer received, and a sheet that rounds only for the export can show unrounded amounts.
  • Company details or a letterhead image/PDF: File → Offer → Offer: settings.

These variable names fill in the document when you set them:

VariableEffect
DocumentTitle = "Estimate"replaces the word "Offer" in title, heading and file name
OfferDate = todaythe date (default: today); text or date(…)
OfferNumber = "2026-014" · ValidUntil = date(2026, 10, 15)reference and validity
CustomerName · CustomerAddressthe address block; \n for new lines
VatRate = 19%net, VAT and gross under the last row; leave out for no VAT
Details
  • An item label may be several words but must start with a letter. A label starting with a digit (3D print: 45 €) reads as a calculation — quote it: "3D print": 45 €. A quoted whole line prints as a paragraph.
  • VatRate applies to the last row of the table, so a discount chained onto total is taxed after the discount. VatRate = 19 and VatRate = "19%" mean the same as 19% (and VatRate = 1 is 1 %); VatRate = "" means no VAT. A rate the offer cannot read is reported before export.
  • The CSV is ;-separated with plain numbers (no unit) and holds only the item tables, including the VAT rows.

Files and data

CSV import and export

Data is loaded and saved by hand: any line with csv(…) or csvwrite(…) gets a button in its result.

CallParametersResult
csv("name")"name": a label for the file, not a path; the button opens a file dialogthe first column of the picked file
csv("name", n)n: the column, counted from 1column n
csv("name", "header")"header": the heading of the wanted columnthe column with that heading
csvwrite(data, "name")data: a number, truth value, list, matrix or list of rows. "name": a label for the save buttona save button for data
1month = csv("sales", 1)
2units = csv("sales", "units")
3csvwrite(units * 2, "forecast")

Quantities are written as their number — 1 h 30 min is 1.5, 5 km + 300 m is 5.3 — so say the unit in the name or a header cell. A text cell that starts with =, +, - or @ gets a leading ', so a spreadsheet reads it as text and runs no formula.

The name is a label, not a path: the button opens a file dialog. Calls with the same name share one file, so you pick it once; different names load different files. Loaded data stays in memory for this draft — after a restart, load again. Separator (;, tab, ,), header row and decimal comma are detected.

A column of numbers becomes a number list; a column with text becomes a list of texts and numbers. Missing cells read as NaN.

Files, libraries and sharing

FileContents
.dplya sheet — plain text with a small header
.dplyliba library of functions and units
.dplybundlea sheet together with its libraries

Libraries. import "units.dplylib" loads the functions and units of a library; Library → Share exports a sheet's functions as one. The libraries that come with draftply (Library → Standard Libraries) apply only in a sheet that imports them.

Sharing. File → Share sends the sheet through the system share sheet — as a .dplybundle if it imports libraries, so it still works for the recipient (iOS, Android, macOS, Windows).

Opening. File → Open and drag & drop (macOS, Windows, Linux) link the draft to its file, so Save writes back. A file opened from another app is copied in and not evaluated until you press Evaluate in the banner. Mail attachments may need saving before they can be dragged in.

The app

Editor

  • Autocomplete suggests names as you type; Tab or Enter accepts.
  • Parameter hint: inside a call the missing parameters show in grey — fv(0.05 shows , nper, pmt[, pv]). Optional ones are in brackets. Your own functions get the same hint.
  • Brackets: the partner of the bracket at the cursor is highlighted; a bracket without partner is red, and the line says what is missing (Unclosed bracket: 1× ) missing).
  • Search and replace: the magnifier in the toolbar or Ctrl + F / Ctrl + H. The arrow on the left of the search bar opens the replace field.
  • Copy a result: select it in the result column; on touch screens press and hold it.

Keyboard shortcuts

On macOS use ⌘ instead of Ctrl.

ShortcutAction
Ctrl + N · Ctrl + Onew draft · open file
Ctrl + S · Ctrl + Shift + Ssave · save as
Ctrl + Pprint / save as PDF
Ctrl + Fsearch
Ctrl + H (macOS ⌘ + ⌥ + F)replace — Enter one, Ctrl + Enter all
Ctrl + Z · Ctrl + Shift + Z / Ctrl + Yundo · redo
Tab · Shift + Tabindent · outdent the selected lines
Ctrl + /comment out / in
Ctrl + Einsert {} or wrap the selection
Ctrl + ,settings
Ctrl + Shift + Mnote mode
Ctrl + Shift + Talways on top
Ctrl + Shift + Nnew window

Drafts and windows

The drafts sidebar (View → Drafts) holds your open drafts. A dot shows whether a draft is linked to a file.

  • Double-click a name to rename it; the pin moves it to the Pinned section at the top.
  • × closes a draft. A file-linked draft just closes (unsaved changes are offered for saving); a plain draft has no file, so its content is discarded after asking.
  • Close all (next to +) closes every unpinned draft after one question.

Several windows: File → New window (Ctrl + Shift + N, on a Mac ⌘ + Shift + N); on Windows and Linux also by starting draftply again. All windows share the sidebar, but a draft is open in one window at a time — elsewhere it is marked ↗ and locked. Each window is titled with its draft's name, so you find it in the taskbar or the window switcher. On a Mac all windows belong to one app with one Dock icon.

Always on top: View → Always on top (Ctrl + Shift + T). On Linux this depends on the window manager; GNOME on Wayland does not offer it.

Note mode

View → Note mode (Ctrl + Shift + M) shrinks draftply to a narrow note that stays above other windows. Results appear faintly behind the text of their line:

1train = 2 * 89 = 178
2Hotel {nights = 3} nights, {nights * 142} → 426 in all

Text lines and errors stay quiet; results in the middle of a line update when you leave the line, so the sentence does not jump while you type. The bar holds the drafts, a { } button, a pin to drop the note behind other windows, and the way back. Leaving restores the window. The .dply file is the same in both views.

A draft shown as a form stays a form in note mode — a compact input mask without the menu bar. With Pro, drafts that have a form get a form button in the bar to switch between form and text; while the form is shown, { } is greyed out.

On phones and tablets note mode is simply a view, switched under View → Note mode.

Narrow windows and phones

Below 600 pt width, results move from their own column to the end of each line. This is not just for phones: pull the window narrow next to your work and it calculates alongside. View → Layout picks Automatic, Compact or Three columns for this draft; the default is in Settings → Editor.

  • Charts span the full width; on a phone in portrait, charts and tall matrices start collapsed.
  • A long result wraps over the rows of its line when the editor line wraps; on a single-row line it is shortened with “…”.
  • Tap a result to jump to the end of its line; press and hold to copy it.
  • The toolbar drops its menu labels as space runs out; when even the icons do not fit, it scrolls (arrows at the edges).
  • On phones and tablets a key bar above the keyboard offers = % ^ , : #, the pairs () {} [] "", @, in and ...

Settings

The settings dialog runs from how it looks to how it behaves: appearance, language, number format, date format, editor.

Appearance

  • Theme: Mocha (dark), Latte (light), Nord (dark), and Contrast Dark / Contrast Light with at least 7:1 contrast. Follow system theme picks Mocha or Latte — and the contrast theme when Windows or Linux ask for high contrast.
  • Layout: Automatic, Compact or Three columns (see Narrow windows).
  • Font sizes: separate sliders for the editor, the interface and the form.

Language

The language of the interface and of all messages.

Number format

  • Decimal separator: Automatic (from your system language), Period (1,234.56) or Comma (1.234,56). In comma mode separate values with ;. In period mode a comma only groups thousands (1,234) or separates values (sum(10,20) → 30). A comma between digits without a space is read as thousands when it looks like one — sum(1,234) is sum(1234), [100,200] is the single number 100200 — and a line that fails for that reason says so; write sum(1, 234) with a space. The editor underlines such numbers. Files and drafts are saved with a decimal point and commas between values; in comma mode draftply shows them with decimal commas and ;, and changing the mode converts open drafts.
  • Thousands separator: on or off.
  • Default precision: the decimal places of results; calculations stay exact.

Date format

From your device, or a fixed order.

Editor

  • Recalculation: live while you type, or when you leave the line.
  • Show indentation: leading spaces as dots.