Guide / Functions and expressions

Functions and expressions

AppNoy doesn't have a "function library" in the style of UPPER() or DATE(). Instead there's a small, precise expression language built entirely on the {{...}} syntax, including chainable filters, and dedicated filters for passwords and encryption.

A. Inserting variables into text

Any text field (an element's text, a toast message, a link, etc.) can contain {{name}}, which gets replaced with its value at runtime. It's resolved in order: first "system tokens" (see the table below), then defined variables, and if nothing is found - empty text is shown.

B. System tokens

Token Returns
{{now}} Current date and time, formatted per language
{{today}} Current date in YYYY-MM-DD format
{{epochMs}} Milliseconds since epoch, as text - handy for chronological sorting
{{uuid}} A UUID v4-style unique ID
{{random}} Random integer between 0 and 999999
{{genId}} A long, time-based unique ID (recommended when you need reliable identification)
{{shortId}} A short 3-digit code (100–999) - easy to type, but not guaranteed unique
{{joystickX}} / {{joystickY}} Live -1..1 position of a Joystick element (game builder), refreshed continuously while it's held
{{tiltX}} / {{tiltY}} Live -1..1 device tilt, once the "Enable tilt controls" action has run
{{lastKey}} The key (e.key - "Enter", "ArrowUp", a letter, etc.) that most recently triggered a "Key pressed" event - see the "Events" chapter

C. Filters (with |)

You can pipe a value through a filter written as {{name|upper}}. Here are all the existing filters:

Category Filter What it does
Basic text upper Converts to uppercase
lower Converts to lowercase
trim Removes whitespace from the start and end
Text formatting capitalize Capitalizes only the first letter
title Capitalizes the first letter of every word
reverse Reverses the character order
length Number of characters in the text
Cleanup & detection digitsOnly Keeps only digits (handy for cleaning up phone numbers)
slug Converts to a URL-friendly string: lowercase letters and hyphens instead of spaces/symbols
mask Hides everything except the last 4 characters (••••1234) - good for showing sensitive info without exposing it
Numbers round Rounds to a whole number
currency Adds ₪ and thousands separators, two digits after the decimal point
percent Adds % at the end
Security & privacy hash One-way - for storing passwords, see section D right below
encrypt Reversible - for encrypting data for storage, see section E right below
decrypt Turns encrypt back into the original text

Chaining several filters in one field

You can chain several filters in a row on the same value, one after another with | - each filter gets the result of the one before it:

{{name|trim|lower|capitalize}}

This example strips extra whitespace, lowercases everything, then capitalizes just the first letter - so " danny " and "DANNY" are shown in the same consistent form, "Danny". There's no limit on how many filters you can chain in one go.

D. Storing passwords without storing the password itself (hash)

The hash filter turns any text into a long, fixed code (SHA-256) in a one-way fashion: the same text will always produce exactly the same code, but there's no way to take the code and recover the original text from it. That's exactly why it's used for password checks - you store only the code, not the password itself.

Example: sign-up with a password, then a login check
Two screens: sign-up saves only the hash of the password; login checks whether the hash of what was typed matches what was saved - the password itself is never sent or stored.
Event - sign-up screen
Element interaction
elementId: btn_signup
mode: click
Action
Save to server storage (dbSet)
collection: users
key: {{username}}
fields: [ passwordHash = {{inpw|hash}} ]
Event - login screen
Element interaction
elementId: btn_login
mode: click
Action
Read from server storage (dbGet)
collection: users
key: {{username}}
target: storedHash (property: passwordHash)
Condition
Condition
left: inpw|hash
operator: ==
right: storedHash
yes
Action
Go to screen
screen: home
no
Action
Show message
text: "Incorrect username or password"
Know the limits

This is fine as a simple entry gate for a small app (orders, a loyalty club, a staff area). It's not a substitute for a real, secure login system with a dedicated server - there's no rate limiting, two-factor authentication, or bank-grade SSL certificates here. Don't use this for truly sensitive data (finances, legal documents, etc.).

E. Encrypting data you need to read again (encrypt / decrypt)

Unlike hash, encrypt is reversible: you store an encrypted value in a table, and whenever you need to show it again - you run decrypt on it and get back exactly the original text. Useful when you want some data (a private note, an ID detail) to not appear as plain text to anyone who opens the "Data" page in the dashboard, but the app itself still needs to show it to the right user.

{{sdb.notes[{{userId}}].secret = {{privateNote|encrypt}}}}

And to display that same value again on screen:

{{element[3].text = {{sdb.notes[{{userId}}].secret|decrypt}}}}
This is "obscuring" encryption, not bank-grade encryption

The key for encrypt/decrypt is fixed and embedded in the app's public code (the same JS file every visitor downloads) - meaning it keeps casual eyes from seeing the value (e.g. in the dashboard's data table), but it won't hold up against someone determined who inspects the browser's code. For passwords and anything truly critical to protect, use hash (one-way), not encrypt.

F. "Run code" (advanced) - a mini path language

The Run code action is not real JavaScript (not eval) - it's a small path language for reading/writing that runs inside {{ }} blocks, and supports:

Syntax Meaning
element[N].prop Read/write to element number N on the current screen only (e.g. element[2].text)
db.path.to.value Local storage on this device (a dotted path into an object)
sdb.table[key].col A row in a shared server table - when reached via runCode it's always global (unlike the dedicated dbSet/dbGet actions, whose default is private)
myVar A bare identifier (no dots) - a defined cache variable
path = value Assignment, e.g. {{myVar = 5}}. Blocks can be nested inside each other - the inner one resolves first
a + b, a - b, a * b, a / b One operator at a time (with a space on both sides of the sign) - no precedence and no chaining ("2 + 3 * 4" does NOT evaluate as 14; nest {{ }} blocks instead), and no string functions. Dividing by zero returns 0, not an error. That's the entire arithmetic support in the language - enough for counters, and for basic game math like gravity/speed

A common example - a counter that goes up by 1 on every click:

{{sdb.scores[id].points = {{sdb.scores[id].points}} + 1}}

G. Condition expressions

The simple condition action ("Condition") is just left-side / operator / right-side. Available operators: == equals, != not equal, > greater than, < less than, >=, <=, contains, empty, notEmpty. Full detail in the "Conditions" chapter.

When you need more complex logic, there's "Compound condition (free text)" - a real grammar with AND/OR/NOT and parentheses, for example:

(age >= 18 OR vip) AND NOT banned