Explore all the solutions you can create with Paperform: surveys, quizzes, tests, payment forms, scheduling forms, and a whole lot more.
See all solutionsConnect with over 2,000 popular apps and software to improve productivity and automate workflows
See all integrationsExplore all the solutions you can create with Paperform: surveys, quizzes, tests, payment forms, scheduling forms, and a whole lot more.
See all solutionsConnect with over 2,000 popular apps and software to improve productivity and automate workflows
See all integrationsThis page features many of the most popular calculations, but it barely scratches the surface of what’s possible. Feel free to copy any of these over to your own forms as a starting place, then add to them, combine them, experiment, and reach out to the support team if you have any trouble.
Our cheat sheet is more of a reference page with examples, as opposed to a step-by-step guide. It assumes that you’re already familiar with the following information:
{{ id.total }}
or {{ id || date d/m/y }}
), check out the sections on dot notation and transformations.Each of these example calculations uses id
to represent the actual ID for a relevant question on your form. In your version, you will need to replace any of these with the appropriate question IDs by selecting the question with the + menu to the right.
If you see any dot notation or a transformation, make sure to include that after the question ID. For example, you might see {{ id.postcode }}
in an example calculation. If your question’s ID is 12345
, then you would need to use {{ 12345.postcode }}
.
If the notation mentions SKU, then a specific product’s SKU must also be inserted. In the example {{ id.quantities.sku }}
, your version might end up looking something like {{ 12345.quantities.ABCD }}
.
Feel free to skip around using the table of contents to the left, or you can search for specific keywords using the “Find” shortcut in your browser (Ctrl-F on Windows or ⌘-F on Mac). You’ll also find some general tips at the very end.
Simple math is often all you need to get the job done. Though not necessary in every case, NUMBERVALUE
can help to ensure that you're working with numbers instead of strings.
length = NUMBERVALUE( {{ id-1 }} );
width = NUMBERVALUE( {{ id-2 }} );
height = NUMBERVALUE( {{ id-3 }} );
length * width * height;
Combining two or more strings of text is called concatenation.
"Hello, " || {{ id }} || "!";
Concatenation can be done with either the shortcut ||
or the full function. Both are used in this guide, depending on which one is easier to read in context.
CONCATENATE( "Hello, ", {{ id }}, "!" );
Don't forget to account for spaces inside the quoted strings.
The SWITCH
function is useful for a variety of applications. This example checks which location is selected from a Dropdown question, then returns an associated employee's email address. The result could then be piped into the "To" field of a custom email.
SWITCH( {{ id }},
"Location A", "employee-1@example.com",
"Location B", "employee-2@example.com",
"Location C", "employee-3@example.com",
"fallback@example.com"
);
This could be used to find the number of choices selected in a Multiple Choice or Dropdown field, or to count the number of files uploaded to a File or Image Upload question.
COUNT( {{ id.array }} );
Combine first and last name fields into one full name with proper capitalization.
full_name = {{ first-name-id }} || " " || {{ last-name-id }};
PROPER( full_name );
Combine required first and last name fields with an optional middle name.
first_name = {{ first-name-id }};
middle_name = {{ middle-name-id }};
last_name = {{ last-name-id }};
full_name = CONCATENATE(
first_name, " ",
IF( middle_name != "", middle_name || " ", "" ),
last_name
);
PROPER( full_name );
The string "\n"
can be used to add a line break.
Split the address into multiple lines and set the punctuation.
street = {{ id.street }};
city = {{ id.suburb }};
state = {{ id.state }};
zip = {{ id.postcode }};
country = {{ id.country }};
CONCATENATE(
street, "\n",
city, ", ", state, " ", zip, "\n",
country
);
Automatically abbreviate the state when formatting an address, if the matching state is found (not case-sensitive). If a match isn't found, display the state as it was typed in.
street = {{ id.street }};
suburb = {{ id.suburb }};
state = {{ id.state }};
postcode = {{ id.postcode }};
stateAbbr = SWITCH( LOWER( state ),
"new south wales", "NSW",
"northern territory", "NT",
"queensland", "QLD",
"south australia", "SA",
"tasmania", "TAS",
"victoria", "VIC",
"western australia", "WA",
state
);
CONCATENATE(
street, "\n",
suburb, " ", stateAbbr, " ", postcode
);
Reformat the result of an Appointment question with the mode set to “Days.”
Your reservation is from Mon, Feb 13th to Fri, Feb 17th.
check_in = DATEFORMAT( {{ id.start }}, "ddd, MMM Do" );
check_out = DATEFORMAT( {{ id.stop }}, "ddd, MMM Do" );
CONCATENATE(
"Your reservation is from ", check_in, " to ", check_out, "."
);
Reformat the result of an Appointment question with the mode set to “Minutes.”
Your appointment is from 10:00 am to 10:30 am on January 20, 2023.
date = DATEFORMAT( {{ id.start }}, "MMMM D, YYYY" );
start = DATEFORMAT( {{ id.start }}, "h:mm a" );
end = DATEFORMAT( {{ id.stop }}, "h:mm a");
CONCATENATE(
"Your appointment is from ", start, " to ", end, " on ", date, "."
);
Add a currency symbol and round to two fixed decimal places (e.g. $12.50
).
total = NUMBERVALUE( {{ id }} );
"$" || FIXED( total, 2 );
Round to a whole number, then add a currency symbol and thousands separator (e.g. $1,234
).
total = NUMBERVALUE( {{ id }} );
rounded = ROUND( total );
NUMBERFORMAT( rounded, ",", ".", "$", "" );
Combine a thousands separator with two fixed decimal places (e.g. $1,234.50
). This one requires a bit of regex, because FIXED
and NUMBERFORMAT
don’t play nice with one another.
total = NUMBERVALUE( {{ id }} );
rounded = ROUND( total, 2 );
formatted = NUMBERFORMAT( rounded, ",", ".", "$", "" );
IF(
REGEXMATCH( formatted, "\\.[0-9]{2}" ),
formatted,
IF(
REGEXMATCH( formatted, "\\.[0-9]{1}"),
formatted || "0",
formatted || ".00"
)
);
Custom Pricing Rules can be used to dynamically alter the total amount charged by the form at checkout.
You can also combine them with calculations for more complex pricing structures. For example, this rule would add the result of a calculation field to the grand total:
Please note that if you use Coupons, you may run into issues when combining them with Custom Pricing Rules (or when using them with calculations in general). These features are usually compatible with one another, but it's worth a little extra testing to make sure that all of your fees and/or discounts are being applied in the correct order.
When working with prices, sometimes you’ll need to reference the total charged by your form. The value {{ total }}
is disabled in calculations, so you will need recalculate it using the individual totals from each Product and Price field. Save the result to a variable, so that you can reference it later.
total = {{ id-1.total }} + {{ id-2.total }} + {{ id-3.total }};
// ...
If you have any preexisting Custom Pricing Rules, these may need to be recreated as well.
services = {{ id-1.total }} + {{ id-2.total }} + {{ id-3.total }};
start_up_fee = IF( {{ id-4 }} == "I am a new customer.", 30, 0 );
total = services + start_up_fee;
// ...
If you'd like to offer discounts at certain quantities, you can start by using a calculation to find out how many products are selected.
Find the quantity of a specific product.
{{ id.quantities.sku }};
Find the total quantity from multiple fields.
{{ id-1.quantity }} + {{ id-2.quantity }};
In some cases, this will be enough info from the calculation, and your Custom Pricing Rules can take care of the rest. For example, the following rule applies a $25 discount (or 25 of your currency) when the "# Selected" calculation shows that at least 10 products have been ordered:
Depending on your pricing structure, you may want to use a calculation to determine the specific amount of the discount instead.
quantity = {{ id.quantity }};
discount_each =
IF( quantity < 10, 0.00,
IF( quantity < 20, 1.50,
IF( quantity < 30, 3.00,
IF( quantity >= 30, 4.50 )
)
)
)
;
quantity * discount_each;
The tips at the end of this guide include two alternative ways to configure this calculation.
In this case, simply subtract the calculation's answer from the total using your Custom Pricing Rule.
This calculation is a part of our method for optionally accepting payments at a later time. It finds the amount that should still be owed after the form is submitted: the total price if paying later, or 0
if paying now.
total = {{ id-1.total }} + {{ id-2.total }} + {{ id-3.total }};
is_paying_later = {{ multiple-choice-id }} == "Pay later";
IF( isPayingLater, total, 0 );
The result should be subtracted from the form’s current total using a Custom Pricing Rule.
This method accomplishes two things. If paying later, the form’s total will be brought down to zero, allowing submission without payment. In that case, the calculation field’s answer (included in the submission results) will also let you know how much the customer still owes.
Used for daily/nightly fees (like a hotel) or hourly fees (like a contractor), these calculations determine the price of a service based on the amount of time selected in an Appointment field.
You can use a Custom Pricing Rule to add the calculation’s result to the total amount charged by the form.
$50 per hour
hours = {{ id.minuteLength }} / 60;
50 * hours;
$60 flat fee + $35 per hour
hours = {{ id.minuteLength }} / 60;
60 + ( 35 * hours );
$100 per night
100 * {{ id.dayLength }};
$100/night for the first week, then $75/night after that
nights = {{ id.dayLength }};
short_stay = nights * 100;
long_stay = 700 + ( ( nights - 7 ) * 75 );
IF( nights > 7, long_stay, short_stay );
The notation
.dayLength
counts the "difference" between dates, so a Monday through Friday would return4
. To include both the first and last day, use{{ id.dayLength }} + 1
instead.
When appearing after submission (e.g. in an email), the piped answer to a Products question includes the name, SKU, price of the selected item. If you only want to display a product's name by itself, you can run the answer through a calculation.
selected = GET( {{ id.selectedProducts }}, 1 );
SWITCH( selected,
"sku-A", {{ id.names.sku-A }},
"sku-B", {{ id.names.sku-B }},
"sku-C", {{ id.names.sku-C }},
"None"
);
Please note that the above calculation only works if the question only allows a single product to be selected. If "can choose more than one product" is toggled on, then you'll likely want to include more information, like the quantity. In that case, try the order summary below and simplify it as needed.
If you accept online payments, an itemized summary is automatically shown on the checkout screen. For quote/estimate forms that don’t take payment, or if you'd like to display these details earlier in the form, you may want to create your own order summary.
// --- enter all ID(s) & SKUs ---
SKUs = ARRAY( "sku-A", "sku-B", "sku-C" );
names = ARRAY(
{{ id.names.sku-A }},
{{ id.names.sku-B }},
{{ id.names.sku-C }}
);
quantities = ARRAY(
{{ id.quantities.sku-A }},
{{ id.quantities.sku-B }},
{{ id.quantities.sku-C }}
);
prices = MAP(
ARRAY(
{{ id.prices.sku-A }},
{{ id.prices.sku-B }},
{{ id.prices.sku-C }}
),
"= FIXED( item, 2 )"
);
selected = {{ id.selectedProducts }};
// --- build the summary ---
totals = MAP( prices,
"= FIXED( NUMBERVALUE( item ) * GET( quantities, index ), 2 )"
);
summary = MAP( selected,
"= CONCATENATE(
GET( names, MATCH( SKUs, item ) ),
' ($',
GET( prices, MATCH( SKUs, item ) ),
') x ',
GET( quantities, MATCH( SKUs, item ) ),
' = $',
GET( totals, MATCH( SKUs, item ) )
)"
);
JOIN( summary, "\n" );
To include information from more than one Products question in the same summary, use the ARRAY
function in the selected
variable.
selected = ARRAY(
{{ id-1.selectedProducts }},
{{ id-2.selectedProducts }},
{{ id-3.selectedProducts }}
);
When the ERROR
function returns a message in a visible calculation field, the pagination and submit buttons will be disabled. There are a couple of different ways to utilize this to add custom error messages to your forms.
One option is to set up a calculation to display either a success message or an error message, depending on whether a certain condition is met.
date_of_birth = {{ id }};
age = DATEDIFF( NOW(), date_of_birth, "years" );
IF( age >= 18,
"Please continue.",
ERROR( "You must be at least 18 to submit this form." )
);
Remember, this calculation field would need to be visible in order for the error to work.
Another popular choice is to set a calculation up so that it always returns an error. Then, you can rely entirely on visibility logic to determine when it appears on the form. Using this method, no message appears unless there is an issue.
If the condition for your error is too complicated to be set up using the basic visibility logic options, consider adding a second calculation, just for the condition itself. Then, refer to the result of that calculation in the visibility logic for the one containing the error message.
In the example above, there would need to be another hidden calculation on the form called "Age," which returns the submitter's age from the Date of Birth question.
date_of_birth = {{ id }};
DATEDIFF( NOW(), date_of_birth, "years" );
If the Date of Birth question is answered, and the result of "Age" is less than 18, then the error message appears.
The following calculations return either true
or false
. They are often used as conditions for error messages, referenced by logic, or saved to a variable as one piece of a larger calculation.
Case-sensitive:
{{ id }} == "Accepted answer";
Not case-sensitive:
LOWER( {{ id }} ) == "accepted answer";
This calculation covers a broad range of use cases—you can use it to check if a postcode matches those in your delivery area, or to find out if an email belongs to one of your employees.
Case-sensitive:
answer = {{ id }};
accepted = ARRAY( "Accepted 1", "Accepted 2", "Accepted 3" );
INCLUDES( accepted, answer );
Not case-sensitive:
answer = LOWER( {{ id }} );
accepted = ARRAY( "accepted 1", "accepted 2", "accepted 3" );
INCLUDES( accepted, answer );
Note that in the second version, the values in the
accepted
array are all written in lowercase. The answer given on the form will first be transformed to lowercase, and then it will be checked against your list of accepted, lowercase values.
This example checks the postcode property of an Address field to make sure that the answer is in US ZIP code format (a five-digit code between “00001” and “99951”).
zip = {{ id.postcode }};
AND(
LEN( zip ) == 5,
REGEXMATCH( zip, "[0-9]{5}" ),
NUMBERVALUE( zip ) >= 1,
NUMBERVALUE( zip ) <= 99951
);
If you'd like to validate the answer to a standalone question instead, remove .postcode
from the first line. When requesting numeric codes like postcodes or ID numbers, we actually recommend using Text fields (as opposed to Number fields, which automatically remove leading zeroes).
For an alternative without calculations, you could use a custom format for a Phone Number question for different types of numeric codes. If you go that route, you might also want to change the wording for its error message, which is normally "Please enter a valid phone number."
Combine these with custom error messages to disable submission when certain email addresses are entered.
Restrict specific email domains. Returns true
if forbidden (matching provider found).
email = LOWER( {{ id }} );
prohibited = ARRAY( "gmail", "yahoo", "aol" );
provider = REGEXEXTRACT( email, "@([a-zA-Z0-9-]*)\\." );
INCLUDES( prohibited, provider );
Only allow company domain. Returns true
if allowed (matching domain found).
email = LOWER( {{ id }} );
domain = REGEXEXTRACT( email, "@([a-zA-Z0-9-\\.]*)" );
domain == "paperform.co";
Our built-in Scoring feature is mostly used for keeping track of correct and incorrect answers. For more complex scoring systems, you might want to use a calculation instead.
This type of quiz or survey gives weighted points based on answers along a range, like a Likert or semantic differential scale.
If each question's answer choices are worded and scored the same way, then you can tally the scores for all questions at once using this shortcut with MAP
:
answers = ARRAY( {{ id-1 }}, {{ id-2 }}, {{ id-3 }} );
scores = MAP(
answers,
"= SWITCH( item,
'Never', 1,
'Rarely', 2,
'Sometimes', 3,
'Often', 4,
'Always', 5,
0
)"
);
SUMIF( scores );
This type of quiz sometimes includes specific questions with “inverted” scoring. In other words, the highest-scoring choice for some questions could be “Always,” while the best answer for others might be “Never.”
standard_answers = ARRAY( {{ id-1 }}, {{ id-2 }}, {{ id-3 }} );
inverted_answers = ARRAY( {{ id-4 }}, {{ id-5 }}, {{ id-6 }} );
standard_scores = MAP(
standard_answers,
"= SWITCH( item,
'Never', 1,
'Rarely', 2,
'Sometimes', 3,
'Often', 4,
'Always', 5,
0
)"
);
inverted_scores = MAP(
inverted_answers,
"= SWITCH( item,
'Never', 5,
'Rarely', 4,
'Sometimes', 3,
'Often', 2,
'Always', 1,
0
)"
);
SUMIF( standard_scores ) + SUMIF( inverted_scores );
Finally, if the answers to each question are worded differently, you would need to split them up into separate SWITCH
functions instead.
q1_score = SWITCH( {{ id-1 }},
"Never", 1,
"Rarely", 2,
"Sometimes", 3,
"Often", 4,
"Always", 5,
0
);
q2_score = SWITCH( {{ id-2 }},
"Strongly Disagree", 1,
"Disagree", 2,
"Indifferent", 3,
"Agree", 4,
"Strongly agree", 5,
0
);
q3_score = SWITCH( {{ id-3 }},
"Poor", 1,
"Needs improvement", 2,
"Good", 3,
"Great", 4,
"Excellent", 5,
0
);
q1_score + q2_score + q3_score;
This calculation keeps track of a separate score for each possible result (e.g. personality type), then returns the name of the highest-scoring result. This answer can be used for the logic in Dynamic Success Pages to display the page for the matching result after the form is submitted.
Statements like variable == "Answer"
resolve to Boolean values (either true
or false
). When Booleans are used in math, true
is treated as 1
and false
becomes 0
. So, true + true + false
would equal 2
. This is used to tally the scores for each result.
Make sure that the text of each answer matches the option in the original question exactly. These are case-sensitive and any extra spaces or characters will also affect the score.
// store each selected answer
q1 = {{ id-1 }};
q2 = {{ id-2 }};
q3 = {{ id-3 }};
q4 = {{ id-4 }};
q5 = {{ id-5 }};
// tally the score for each category by matching the answers
result_A =
( q1 == "Answer for result A in question 1." ) +
( q2 == "Answer for result A in question 2." ) +
( q3 == "Answer for result A in question 3." ) +
( q4 == "Answer for result A in question 4." ) +
( q5 == "Answer for result A in question 5." )
;
result_B =
( q1 == "Answer for result B in question 1." ) +
( q2 == "Answer for result B in question 2." ) +
( q3 == "Answer for result B in question 3." ) +
( q4 == "Answer for result B in question 4." ) +
( q5 == "Answer for result B in question 5." )
;
result_C =
( q1 == "Answer for result C in question 1." ) +
( q2 == "Answer for result C in question 2." ) +
( q3 == "Answer for result C in question 3." ) +
( q4 == "Answer for result C in question 4." ) +
( q5 == "Answer for result C in question 5." )
;
// find and return the highest-scoring category
highest_score = MAX( result_A, result_B, result_C );
SWITCH( highest_score,
result_A, "Result A",
result_B, "Result B",
result_C, "Result C",
"Error"
);
But what if you'd like to make changes to the quiz answers later, without it messing up your calculation? You can score the quiz based on the position (or "index") of each answer, rather than matching the full text.
The dot notation .selectedIndex
pulls the index of the selected answer to a multiple-choice question. For example, the index of the second choice in the option list would be the number 2
.
This saves you from copying and pasting full answers, as well as making it easier to make changes to the quiz.
// store the index of each answer
q1_index = {{ id-1.selectedIndex }};
q2_index = {{ id-2.selectedIndex }};
q3_index = {{ id-3.selectedIndex }};
q4_index = {{ id-4.selectedIndex }};
q5_index = {{ id-5.selectedIndex }};
// tally the score for each category by matching the answers' indexes
blue =
( q1_index == 2 ) +
( q2_index == 1 ) +
( q3_index == 2 ) +
( q4_index == 3 ) +
( q5_index == 3 )
;
green =
( q1_index == 1 ) +
( q2_index == 2 ) +
( q3_index == 1 ) +
( q4_index == 2 ) +
( q5_index == 2 )
;
red =
( q1_index == 3 ) +
( q2_index == 2 ) +
( q3_index == 3 ) +
( q4_index == 1 ) +
( q5_index == 1 )
;
// find and return the highest-scoring category
highest_score = MAX( blue, green, red );
SWITCH( highest_score,
blue, "Blue",
green, "Green",
red, "Red",
"Error"
);
For a shortcut, consider setting up the original questions such that the answers are always in the same order. Place all of the answers associated with a certain result first, another result second, and so on. Then, toggle on the "randomize option order" setting for each question.
The index of each answer will correspond to a specific result, but they won't appear in the same order for the quiz taker. This allows you to simplify the calculation:
// store the index of each answer into a single array
answers = ARRAY(
{{ id-1.selectedIndex }},
{{ id-2.selectedIndex }},
{{ id-3.selectedIndex }},
{{ id-4.selectedIndex }},
{{ id-5.selectedIndex }}
);
// count the number of times each category's index number appears
blue = COUNTIF( answers, 1 );
green = COUNTIF( answers, 2 );
red = COUNTIF( answers, 3 );
// find and return the highest-scoring category
highest_score = MAX( blue, green, red );
SWITCH( highest_score,
blue, "Blue",
green, "Green",
red, "Red",
"Error"
);
This calculation leverages options selected by a respondent on the form to assign a given value, providing a value alongside the question title corresponding with the specified option.
For the purposes of this demonstration, it is assumed your form uses Dropdown fields and extracts the selected options using the {{ id.array }}
dot notation.
// Define the individual answer arrays for each question
q1 = {{ id-1.array }};
q2 = {{ id-2.array }};
q3 = {{ id-3.array }};
// Initialise the summary array (where we'll keep the results)
final_summary = ARRAY();
// Process question 1
final_summary = PUSH(
final_summary,
ARGS2ARRAY(
"Question 1",
MAP(q1, "
// Compare each index key for `q1` with the pre-determined values
SWITCH(item,
'Choice 1', '0',
'Choice 2', '1',
'Choice 3', '2',
'')
")
)
);
// Process question 2
final_summary = PUSH(
final_summary,
ARGS2ARRAY(
"Question 2",
MAP(q2, "
// Compare each index key for `q2` with the pre-determined values
SWITCH(item,
'Choice 1', '3',
'Choice 2', '4',
'Choice 3', '5',
'')
")
)
);
// Process question 3
final_summary = PUSH(
final_summary,
ARGS2ARRAY(
"Question 3",
MAP(q3, "
// Compare each index key for `q3` with the pre-determined values
SWITCH(item,
'Choice 1', '6',
'Choice 2', '7',
'Choice 3', '8',
'')
")
)
);
JOIN(final_summary, "\n");
The function NOW
returns the current date and time. As a form is being filled out, the calculation runs in the browser, and NOW
is automatically adjusted for the visitor's timezone setting. However, when the form is actually submitted, the calculation runs again on the server. This causes NOW
to always return its final answer in UTC.
This can cause a discrepancy between a calculation's visible result on the live form and in the submission results. For this reason, we recommend using DATEFORMATUTC
to normalize NOW
to UTC.
current_utc = DATEFORMATUTC( NOW(), "YYYY-MM-DD HH:mm:ss" );
current_pst = DATESUB( current_utc, 8, "hours" );
hour = DATEFORMAT( current_pst, "H" );
IF( NUMBERVALUE( hour ) < 14,
"Orders placed before 2:00 pm will be available within four hours.",
"Orders placed after 2:00 pm will be available by 9:00 am on the following day."
);
As you can see in the example above, you can manually add or subtract hours from NOW
's UTC result if you'd like to use a specific timezone.
Adjusting this for daylight saving time can be tricky, because in many regions, the dates for DST change every year. One solution is to set yourself up in advance by manually inserting the dates for the next few years or more.
// --- time adjustment ---
utc = DATEFORMATUTC ( NOW(), "YYYY-MM-DD HH:mm:ss" );
pst = DATESUB( utc, 8, "hours" );
pdt = DATESUB( utc, 7, "hours" );
dst = OR(
AND(
DATEISAFTER( pst, "2023-03-12 02:00" ),
DATEISBEFORE( pdt, "2023-11-05 02:00")
),
AND(
DATEISAFTER( pst, "2024-03-10 02:00" ),
DATEISBEFORE( pdt, "2024-11-03 02:00")
),
AND(
DATEISAFTER( pst, "2025-03-09 02:00" ),
DATEISBEFORE( pdt, "2025-11-02 02:00")
)
);
current = IF( dst, pdt, pst );
// --- continue calculation ---
hour = DATEFORMAT( current, "H" );
IF( NUMBERVALUE( hour ) < 14,
"Orders placed before 2:00 pm will be available within four hours.",
"Orders placed after 2:00 pm will be available by 9:00 am on the following day."
);
If you only need to pipe the submission time into an email or integration, you can use {{ submitted_at }} directly instead of finding the time with a calculation. The timezone shown will depend on your account settings. For a different date format, you can also transform it with {{ submitted_at || date d/m/y }}.
Paperform's calculations don't support "truly" random number generation. However, if you need a bit of randomness, you can get around this by applying some math to the current time, in seconds.
DATEFORMATUTC( NOW(), "X" ) % size_of_set;
The variable size_of_set
represents the size of the range of numbers you would like to return, including the number 0
. For example, you can enter 5
to return a pseudo-random number between 0
and 4
:
DATEFORMATUTC( NOW(), "X" ) % 5;
This can be used in a number of ways.
Let's say you'd like to assign each customer who submits your form to a random employee on your team. You could use this calculation to return one of your agents' emails at random, then pipe the result into the "To" field of a custom email.
agents = ARRAY(
"agent-1@example.com",
"agent-2@example.com",
"agent-3@example.com"
);
position = DATEFORMATUTC( NOW(), "X" ) % COUNT( agents ) + 1;
GET( agents, position );
A simpler application would be a workaround for some basic A/B testing. While we don't fully support built-in A/B testing (e.g. switching between two forms that share a single slug), you can reference this calculation in your conditional logic to randomize some part of it, like a section, page, or outgoing email.
is_even = ISEVEN( DATEFORMATUTC( NOW(), "X" ) );
IF( is_even, "A", "B" );
Some of the examples on this page use regex, which is a type of expression used for text validation in many coding languages. Regex is supported in three Paperform calculation functions: REGEXEXTRACT
, REGEXMATCH
, and REGEXREPLACE
. The internet has some great free resources for learning how to write regex, such as RegexOne and RegExr.
Please note that the escape character for Paperform’s calculations is a double backslash \\
. If you’ve used regex in other languages (e.g. Javascript), you might be familiar with a single backslash \
, which will not work in this case.
REGEXEXTRACT( {{ id }}, "@([\\w-\\.]*)" );
The Live Preview found in calculations can help tremendously, but it sometimes thinks that there’s a problem when there isn’t one. One reason for this is that the answers used for the preview don’t always match the format of those that you will receive from your submitters.
To make testing easier, try commenting out any lines that have piped answers and temporarily replacing them with new lines using static values.
// nights = {{ id.dayLength }};
nights = 5;
short_stay = nights * 100;
long_stay = 700 + ( ( nights - 7 ) * 75 );
IF( nights > 7, long_stay, short_stay );
The double forward slash
//
creates a comment. This tells the calculation to ignore the whole line, but you can still see it for your reference.
Using the above calculation, you would know that the answer in the Live Preview should return the number 500
. If the wrong result or an error message appears, this tells you that something might be up with the math or syntax. You can also try out different values for the nights
variable this way, then check the Live Preview to make sure the correct answer comes out every time.
When you’re all set, it’s easy to un-comment the original line so that the calculation works as normal again.
nights = {{ id.dayLength }};
short_stay = nights * 100;
long_stay = 700 + ( ( nights - 7 ) * 75 );
IF( nights > 7, long_stay, short_stay );
The next step is to test using the live form. To do this, toggle off “Hide this question” so that the calculation appears on the page. As you change your answers on the live form, you can watch as the result of your calculation changes in real-time. After you’re done testing, you can hide the field again if you like.
Lastly, it’s important to remember that there are often many different ways to accomplish the same goal. For example, you could use any of the following calculations to return different discount amounts at different price points:
Nested IF
statements.
quantity = {{ id.quantity }};
discount_each =
IF( quantity < 10, 0.00,
IF( quantity < 20, 1.50,
IF( quantity < 30, 3.00,
IF( quantity >= 30, 4.50 )
)
)
)
;
quantity * discount_each;
Replacing a variable.
quantity = {{ id.quantity }};
discount_each = 0.00;
discount_each = IF( quantity >= 10, 1.50, discount_each );
discount_each = IF( quantity >= 20, 3.00, discount_each );
discount_each = IF( quantity >= 30, 4.50, discount_each );
quantity * discount_each;
Using SWITCH
and true
.
quantity = {{ id.quantity }};
no_disc = quantity < 10;
disc_10 = quantity >= 10 and quantity < 20;
disc_20 = quantity >= 20 and quantity < 30;
disc_30 = quantity >= 30;
discount_each = SWITCH( true,
no_disc, 0.00,
disc_10, 1.50,
disc_20, 3.00,
disc_30, 4.50
);
quantity * discount_each;
Each of these solutions would give you the exact same result, so the “best” one is simply the one that makes the most intuitive sense to you.
We do recommend using lots of line breaks, descriptive variable names, and adding comments for internal notes and reminders. Even if you’re the only one who edits the form, making your calculations more readable means that they will be easier to understand and edit when you need to make changes to them later.
If you’re having trouble with your calculations, or if you just have questions about how anything works, please do feel free to reach out and ask the support team. Rather than providing a complete calculation for you, we’ll work together to find the best method for your use case, then help you understand everything you need to do in order to build the full solution out yourself. By keeping you involved in the process, you’ll be empowered to make changes to the calculation in the future, making the solution scalable and sustainable in the long term.
Support is available via the chat icon in the bottom-right corner of the page, or you can email us at support@paperform.co.