No matches — try another term.
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:
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.
| Topic | Free |
|---|---|
| Writing a sheet | notes, // comments, # headings, labels, {…} in text, variables, @n, total |
| Numbers | + - / ^ %, += -= = /=, k/M, percentages, comparison & logic, a if c else b |
| Units | units and currencies inline, in/to, convert, defunit |
| Math | sqrt, cbrt, abs, exp, log, ln, log10, log2, round, floor, ceil, min, max, clamp, sign, pow, mod, hypot, lerp, proportion |
| Trigonometry | sin … atanh, atan2, deg2rad, rad2deg |
| Combinatorics · Random | factorial, gcd, lcm, nCr, nPr · rand, randi |
| Number systems | 0x/0b/0o, hex, bin, oct, dec, band, bor, bxor, bnot, shl, shr |
| Complex numbers | 3i, re, im, conj, arg, roots |
| Electronics | db, db2ratio, dbm, dbm2w, e12, e24 |
| Text · Dates | "…", +, s[i], str, ord, char, print · date, datetime, today, now, days, addmonths, dformat, … |
| Lists · Matrices | writing them, [i], arithmetic ([1, 2] * 2) |
| CSV · Files | csv, csvwrite · import, share, open-with |
| Topic | Pro |
|---|---|
| List functions | len, range, push, pop, first, last, sort, reverse, contains, list, append, keys, values, map, filter, linspace, cumsum, cumprod (also on text) |
| Matrix functions | det, inv, transpose, trace, norm, eye, zeros, ones, size |
| Statistics | sum, mean, median, std, variance, mode, mad, percentile, quantile, corr, cov, linreg |
| Finance | pv, fv, pmt, nper, rate, npv, irr |
| Charts | plot, scatter, bar, pie, hist, surface, scatter3 |
| Algebra · Calculus | solve, simplify, factor · diff, integrate |
| Programming | f(x) = …, def, for/while/if, class |
| Form view · Offer export | a 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: writesqrt(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 3andx y = 5are reported with the fix.
A bracket or a function call may span several lines; draftply keeps reading until it closes:
Comments
// starts a comment, on its own line or behind a calculation:
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.
# 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:
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:
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 like3 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:
totalstops 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:
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:
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:
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:
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:
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
| Operator | Meaning | Example |
|---|---|---|
+ - * / | the four basic operations; × and ÷ read as * and / | 7 / 2 → 3.5 |
^ | power | 2^8 → 256 |
% after a value | percent | 200 * 15% → 30 |
% between values | remainder (modulo) | 10 % 3 → 1 |
( ) | grouping; 3(x + 5) multiplies | (1 + 2)(3 + 4) → 21 |
a..b | whole-number range, for for and plot | 1..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:
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:
| Expression | Result | Reads as |
|---|---|---|
100 + 10% | 110 | 100 plus 10 % of 100 |
250 - 15% | 212.5 | 250 minus 15 % of 250 |
100 + 10% + 10% | 121 | two markups in a row |
1200 * 19% | 228 | 19 % of 1200 |
19% of 250 | 47.5 | 19 % of 250 |
238 / 119% | 200 | net from gross |
100 + (10% + 10%) | 120 | two 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 @:
Details
%binds tighter than*and/, so238 / 119%is238 / 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 + pis4.8,p + 5%is 25 %,p * 2the plain number0.4. Assigning anything else topmakes 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 % 3is2,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
| Operator | Meaning |
|---|---|
< > <= >= | 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:
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:
A quantity can come in parts, each smaller than the one before:
+, - 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:
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
| Quantity | Units |
|---|---|
| Length | nm, µm (um), mm, cm, dm, m, km, in, ft, yd, mi |
| Mass | g, kg, t, oz, lb |
| Time | ns, µs (us), ms, s, min, h, d (day, days), wk (week, weeks) |
| Volume | ml, dl, L (l); US: floz, pt, qt, gal |
| Energy · power | J, kJ, Wh, kWh · W, kW, MW |
| Force · pressure | N, kN · Pa, hPa, kPa, MPa, mbar, bar |
| Frequency | Hz, kHz, MHz |
| Angle | deg (°), 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:
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:
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:
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:
| Form | Meaning |
|---|---|
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.
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 ofconvert, and a sheet that is consistently in a unit you chose (a sheet incmstays incm). 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/100kmis a consumption, not 576 N — unless you named it yourself withdefunit(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^2squares the unit,(54 m)^2the 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
kmand still write100 km; on its own,kmis the variable. - Functions take plain numbers.
exp,log,sqrtof 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,cosandtantake 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:
| Function | Parameters | Result |
|---|---|---|
str(x) | x: a number, list or date | the 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 reported | the 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 them | the character(s), the inverse of ord (char([72, 105]) → Hi) |
print(x) | x: any value | shows it in the result column (see Debug output) |
len, sort, contains | see the list functions | length, 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: " + 5is reported, write"Total: " + str(5)or"Total: {5}"."5" == 5isfalse;<and>do not apply to text.lencounts characters, solen("Käse")is4.containsis case-sensitive.sortignores case and places umlauts afterz; 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
| Function | Parameters | Result |
|---|---|---|
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–59 | a date with time |
today · now | none | today · the current date and time, to the minute |
8:12 | none | a clock time today |
day(d) · month(d) · year(d) · hour(d) · minute(d) | d: a date | that part of it |
weekday(d) | d: a date | 1 = Monday … 7 = Sunday |
days(d1, d2) or d1 - d2 | d1: the later date, d2: the earlier one | difference 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 back | the shifted date, clamped to the month end (addmonths(date(2026, 1, 31), 1) → 02/28/2026) |
monthstart(d) · monthend(d) | d: a date | first · 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 format | as 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:
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.
Named lists
Keys make a list readable by name. Quotes are optional for one-word keys, and the list may span several lines:
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.
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
| Function | Parameters | Result |
|---|---|---|
len(list) | list: a list or a text | number 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 0 | 0 … 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 list | first · last entry |
push(list, x) · pop(list) | list: the list. x: the value to add at the end | with x appended · without the last entry |
sort(list) · reverse(list) | list: numbers, or texts. sort needs all numbers or all texts | sorted 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: numbers | running 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 list | its 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 on | f applied to each entry · entries for which f is true |
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:
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:
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
| Function | Parameters | Result |
|---|---|---|
det(A) · inv(A) | A: a square matrix. inv is reported for a singular one | determinant · inverse |
transpose(A) · trace(A) | A: a matrix | rows and columns swapped · sum of the diagonal |
norm(A) | A: a matrix | Frobenius 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 × n | identity · 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 |
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:
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 as1e-400stay. 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,ceilandroundtreat 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, sosqrt(2)^2 - 2is0— anda == bexactly whena - b == 0. Tiny values are compared relative to their size.- Exact:
sqrtof a perfect square (sqrt(2.25)→1.5),cbrtof a cube,log10/log2of an exact power (log10(1000)→3),sin,cosandtanin 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, andpv,fv,pmt,npvwith 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
| Function | Parameters | Result |
|---|---|---|
sqrt(x) · cbrt(x) | x: any number. A negative x gives an imaginary result for sqrt; cbrt stays real | square root · cube root (cbrt(-8) → -2) |
abs(x) · sign(x) | x: any number. abs keeps a unit; sign always gives a plain number | absolute 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 exponent | eˣ · 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 up | rounded 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 them | smallest · 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 hi | x 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) → -2 | remainder, same as a % b |
hypot(x, y) | x and y: the two legs of a right triangle, exactly two values | sqrt(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 = ? : c | a · c / b (proportion(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).
| Function | Parameters | Result |
|---|---|---|
sin, cos, tan | x: an angle. A plain number is in radians; an angle with unit (30°, 30 deg, 1 rad) is taken as it is | sine, cosine, tangent |
asin, acos, atan | x: a plain number. asin and acos take values from −1 to 1; beyond that the result is complex | inverse 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, atanh | x: a plain number. acosh needs x ≥ 1 and atanh needs −1 < x < 1 for a real result | hyperbolic 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 here | degrees → radians · radians → degrees |
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
| Function | Parameters | Result |
|---|---|---|
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 numbers | greatest 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 more | combinations — order does not matter; 0 when k > n |
nPr(n, k) | n and k as in nCr: n items, k of them picked | permutations — order matters; 0 when k > n |
Randomness
| Function | Parameters | Result |
|---|---|---|
rand() | none | decimal 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 rounded | decimal in [min, max), rounded to digits places if given |
randi(min, max) | min and max: whole numbers (below 2⁵³), both ends possible | a whole number, both ends included |
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).
| Function | Parameters | Result |
|---|---|---|
sum · mean · median · mode | a list, or the values written one after another (mean(2, 4, 6)); never both at once | sum · average · middle value · most frequent value (a tie is reported) |
std · variance | a list, or values as above | sample 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 |
mad | a list, or values as above | median 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 percentile | the 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 median | the value at q, interpolated between entries |
corr(xs, ys) · cov(xs, ys) | xs, ys: two lists of equal length; pairs are formed by position | Pearson correlation · sample covariance |
linreg(xs, ys) | xs, ys: x and y values, as in corr | straight-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).
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).
| Function | Parameters | Result |
|---|---|---|
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 period | payment 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 period | present 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 start | future value of the payments and the start amount |
nper(rate, pmt, pv[, fv]) | rate, pmt, pv, fv as above | number of periods (reported if no positive number reaches the goal) |
rate(nper, pmt, pv[, fv]) | nper, pmt, pv, fv as above | interest 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 later | net present value |
irr(cashflows) | cashflows: a list as in npv | internal 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 nper | the interest part of payment number per, negative when you pay it |
ppmt(rate, per, nper, pv[, fv]) | the same as ipmt | the 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 0 | growth 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 1 | effective yearly rate, (1 + nominal / npery)^npery − 1 |
nomrate(effect, npery) | effect: the effective yearly rate. npery as in effrate | the 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 %).
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:
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.
| Function | Parameters | Result |
|---|---|---|
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 numbers | bitwise AND · OR · XOR |
bnot(a) | a: a whole number | bitwise 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 up | a shifted left · right by n bits |
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.
| Function | Parameters | Result |
|---|---|---|
re(z) · im(z) | z: a complex or a plain number | real part · imaginary part |
abs(z) · arg(z) | z: a complex or a plain number | magnitude · angle in radians |
conj(z) | z: a complex number | conjugate: 2 + 3i → 2 - 3i |
roots(x, n) | x: the number, may be complex. n: the degree, a whole number of 1 or more | all 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.
Complex numbers also work with units, in lists (sum, mean) and matrices, and solve returns complex roots:
Limits
- No order:
1i < 2is reported, and so aresort,min,max,median,percentile,quantileon complex lists — compareabs(z)instead.==and!=work.std,variance,mode,corr,covandlinregtake 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 needOhm^0.5.
Decibels and E-series
| Function | Parameters | Result |
|---|---|---|
db(ratio) · db2ratio(db) | ratio: a power ratio above 0 (db(0) is undefined). db: a level in decibels | power ratio → dB (10·log10) · and back |
dbm(watts) · dbm2w(dbm) | watts: a power in watts, above 0. dbm: a level in dBm | watts → dBm (relative to 1 mW) · and back |
e12(x) · e24(x) | x: a positive number | nearest standard value of the E12 · E24 series, by ratio (e12(2) → 2.2) |
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.
| Call | Draws |
|---|---|
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 |
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
| Chart | Parameters | Defaults |
|---|---|---|
plot | expr…, from..to, yFrom..yTo, steps, "title", "x", "y", "logy" | x −2π..2π, 300 steps (2–10,000) |
scatter | xs, ys (equal length) or xs, [named lists], then labels | — |
bar | values or named list, optional xs, then labels | bars numbered 0, 1, 2 … |
pie | values, labels or a named list, "title" | slices numbered 1, 2, 3 … |
hist | data, bins, then labels | 10 bins (2–200) |
surface | expr, xFrom..xTo, yFrom..yTo, steps, then 4 labels | −2π..2π, y like x, 30 steps (2–200) |
scatter3 | xs, ys, zs, then 4 labels | — |
- A logarithmic axis skips values ≤ 0; tooltips and exports keep the real values.
pieshows 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,zfor 3D;label,valueforpie.
Algebra (CAS) Pro
| Function | Parameters | Result |
|---|---|---|
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 for | solutions 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 written | the value of each unknown |
simplify(expr) | expr: a polynomial in your variables | expanded and combined |
factor(expr) | expr: a polynomial of degree 2 or less | factored |
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:
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. factorstays in the real numbers:factor(x^2 + 1)is irreducible over the reals.simplifydoes not cancelx^2 / xor apply trigonometric identities; those are reported. Powers of an unknown need whole exponents from 0 to 8 (x^2.5is reported); constants may have any exponent (2^0.5 * x).- Complex numbers in the input are not supported.
Calculus Pro
| Call | Parameters | Result |
|---|---|---|
diff(expr, x) | expr: the expression. x: the variable to differentiate by | the derivative, symbolically |
diff(expr, x, point) | point: the value of x where the slope is wanted | the derivative at that point |
integrate(expr, x) | expr: the expression. x: the variable to integrate by | the indefinite integral, symbolically (with + C) |
integrate(expr, x, a, b) | a, b: lower and upper limit of x; inf is allowed as a limit | the definite integral, numerically (steps such as floor(x) cost the last digits) |
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:
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:
A longer one with def, indented below:
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:
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:
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
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.
| Field | What it is |
|---|---|
| Number input | edits a value (capital = 1000 €); an optional unit is shown and kept |
| Text input | edits text (loc = "JN48HU") without the quotes; the " button turns it on |
| Choice | chips such as Yes = 1 / No = 0 |
| Date | a calendar picker, automatically for a variable holding a date |
| Dice | redraws a rand(…)/randi(…) variable |
| Output | a result, read-only but copyable |
| Section | a heading that groups the fields below; tap to fold, ⌄ to start folded |
| Charts · CSV | pick 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:
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.
- Text lines become paragraphs, labeled lines rows of the table, the last row of a table with a
totalthe 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: 1200prints1,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 becomesround(…, 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:
| Variable | Effect |
|---|---|
DocumentTitle = "Estimate" | replaces the word "Offer" in title, heading and file name |
OfferDate = today | the date (default: today); text or date(…) |
OfferNumber = "2026-014" · ValidUntil = date(2026, 10, 15) | reference and validity |
CustomerName · CustomerAddress | the 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. VatRateapplies to the last row of the table, so a discount chained ontototalis taxed after the discount.VatRate = 19andVatRate = "19%"mean the same as19%(andVatRate = 1is 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.
| Call | Parameters | Result |
|---|---|---|
csv("name") | "name": a label for the file, not a path; the button opens a file dialog | the first column of the picked file |
csv("name", n) | n: the column, counted from 1 | column n |
csv("name", "header") | "header": the heading of the wanted column | the column with that heading |
csvwrite(data, "name") | data: a number, truth value, list, matrix or list of rows. "name": a label for the save button | a save button for data |
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
| File | Contents |
|---|---|
.dply | a sheet — plain text with a small header |
.dplylib | a library of functions and units |
.dplybundle | a 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;
TaborEnteraccepts. - Parameter hint: inside a call the missing parameters show in grey —
fv(0.05shows, 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.
| Shortcut | Action |
|---|---|
Ctrl + N · Ctrl + O | new draft · open file |
Ctrl + S · Ctrl + Shift + S | save · save as |
Ctrl + P | print / save as PDF |
Ctrl + F | search |
Ctrl + H (macOS ⌘ + ⌥ + F) | replace — Enter one, Ctrl + Enter all |
Ctrl + Z · Ctrl + Shift + Z / Ctrl + Y | undo · redo |
Tab · Shift + Tab | indent · outdent the selected lines |
Ctrl + / | comment out / in |
Ctrl + E | insert {} or wrap the selection |
Ctrl + , | settings |
Ctrl + Shift + M | note mode |
Ctrl + Shift + T | always on top |
Ctrl + Shift + N | new 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:
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() {} [] "",@,inand...
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)issum(1234),[100,200]is the single number 100200 — and a line that fails for that reason says so; writesum(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.