Skip to main content

PLE code manual for AI helpers

PLE Code uses a restricted TypeScript-shaped language. The editor converts it to Blockly workspace JSON and PLE variable declarations. The existing PLE server compiler and runtime execute the program. Code is never evaluated as JavaScript. TypeScript highlighting does not imply that arbitrary TypeScript syntax or standard JavaScript APIs are supported.

Contents​

Secondary-keyholder permissions​

An approved secondary keyholder with Edit PLE uses the existing editor to inspect, edit, enable and run the lock's Programmable Lock instances, including programs created by the primary keyholder. The ordinary compiler, workspace, functions, variables, expressions, branches, dialogs and event types apply. Edit extensions grants configuration access to other existing extension editors; it does not grant extension backend tokens or controller delegation.

A secondary-authored configuration cannot contain direct lock-end, password-unlock or parental-control commands. Editing a program does not expose combination data, password secrets, private memory profiles, private templates or global groups. Existing private-resource ownership checks still apply. For example, a co-keyholder can change a manual reward to compute its duration using a variable:

on.manual(() => {
const rewardSeconds = 60;
removeTime(rewardSeconds);
});

Saving this program requires Edit PLE. Running it as the secondary keyholder also requires Remove time, and the resolved duration must fit the accepted action and daily allowances. Compile and synthetic draft tests do not affect the live lock. Testing a saved active extension saves and runs the selected event on the real lock, following the normal editor behavior.

Configuration changes persist after the editing grant ends, like ordinary keyholder configuration. Normal future triggers keep the configured extension's authority. A direct secondary run carries the current grant through its effects: each supported mutation checks the relevant permission, current session and bounds. Direct unlock, combination access, ownership, delegation and parental phone control remain excluded. Existing grant-owned scheduled instances retain their provenance until a normal configuration save converts them; their future effects stop when that original grant ends.

Direct secondary runs support resolved add/remove time, freeze/toggle and unfreeze within the approved limits; timer end/remaining-time changes; configured or custom tasks, task completion/timers, calendar events/tasks, cage checks, quizzes, hygienic-break offers, card adjustments, public-voting configuration, allowed settings and daily requirement counters, participant notifications, device commands, and custom-event scheduling/cancellation. Task/calendar/quiz actions require Tasks; calendar management also requires wearer consent. Cage checks require Verification. Card changes require Edit lock limits. Settings, public voting and daily counters require Edit lock settings. Each device command requires its effect permission and live wearer/device eligibility. Quiz consequences support bounded add/remove time or timed freeze; pillory consequences are rejected before generation.

Direct time additions use the same limits as the manual controls. New and existing secondary grants have no extra 24-hour session addition cap while the primary remains trusted. Custom action/daily caps and accepted lock deadlines still apply. Inherited limits follow current primary trust without reinvitation; explicit custom caps remain binding. See shared secondary-keyholder limits.

Custom events scheduled by a direct secondary run retain its grant through the delayed execution and stop after revocation. Timer setters require an unfrozen timer lock and consume the corresponding add/remove allowance; concurrent timer changes reject the write. With a trusted primary, hygienic-break offers use the primary offer rules and support expiresInSeconds; the wearer still has to accept. Untimed random/berserk device starts can run until stopped when the trusted-primary grant has no duration cap. A custom or previously saved duration cap keeps those starts timed, within the existing device maximum.

New and existing trusted-primary grants allow actions.freezeIndefinitely() when no duration cap or maximum deadline applies, and can unfreeze a primary freeze while preserving their agreed deadlines. Direct secondary execution still rejects extended freezes, pillory effects, community gag changes, Coach messages, game-cooldown resets, metadata changes, hygiene-window changes/revocation, and starting a hygienic unlock for the wearer. These are distinct from editing the ordinary configuration. Direct unlock, parental phone control, ownership and delegation remain unavailable.

A timer reaching zero does not end secondary access while an enabled extension still has an unfinished completion requirement. Once the session completes, secondary access ends; later time additions or new requirements do not restore the old grant. See the secondary editor guide.

Instructions for the AI helper​

Use the following rules when writing or modifying a PLE program:

  1. Treat this manual as the language contract. Use only documented syntax, exact API names, option names, and enum values. Do not invent methods from their likely English names.
  2. Return PLE source that the user can paste into the Code editor. Do not return compiled program JSON, Blockly workspace JSON, an npm package, or a script that requires a JavaScript runtime unless the user specifically requests that format.
  3. Prefer compact helpers for simple buttons, conditions, assignments, text, and time changes. Use the generic block API for the remaining capabilities. Both forms use the same compiler. Check each helper's argument count at every call site: text(...) requires 2–32 values; pass a single string directly without wrapping it.
  4. Preserve existing variable names, named(...) mappings, function names, parameter names, custom-event slots, and opaque metadata when editing an existing program. Do not regenerate IDs. Make the smallest change that fulfills the request.
  5. Declare workspace variables at the top level. Use event properties only inside that event's callback. Pass event data into functions through declared function parameters.
  6. Distinguish literal configuration fields, expression inputs, variable selectors, and callback branches. They are not interchangeable. Check the API table before filling an option.
  7. Use seconds for raw durations and date-input timestamps; convert the explicitly documented millisecond event fields before using them as date inputs. Use duration(...), dateTime(...), or relativeDateTime(...) when that makes the intent clearer. Never substitute JavaScript millisecond timestamps.
  8. State prerequisites that affect the requested behavior: for example the Task Module, a shared lock, calendar permission, or a compatible connected device. A handler does not enable the feature that produces its event.
  9. Explain assumptions about counts, cooldowns, recipients, dates, and behavior on failure. If the user says only "freeze", clarify whether they want a timed or indefinite freeze, or explicitly identify your chosen interpretation as an assumption. Unspecified duration ranges and cooldowns are not product defaults; do not silently add them. Ask for real existing resource IDs when an operation requires one; do not fabricate task, profile, app, or geofence IDs.
  10. Supply a brief explanation and relevant simulation steps. Do not claim code was compiled, simulated, saved, or deployed unless that actually happened. Treat compiler warnings as actionable: a syntactically valid block may still be skipped by the compiler.
  11. Read the complete entry for every API used, including its option table, prerequisites, and failure behavior. If a file read is truncated or ends halfway through an entry, retrieve the rest before writing code. Use the output comparison below to distinguish coach chat, coach nudges, PLE UI output, notifications, and history.
  12. For visible coach chat, explicitly choose senderRole: "wearer" or "keyholder" with the intended attribution. The default "programmable_lock" is an internal nudge and does not display the supplied text as a chat message. Do not promise visibility and then suggest changing roles if it fails.
  13. State what this manual does not establish instead of guessing runtime behavior or relying on repository access. If the intended behavior remains ambiguous, ask a focused question. Describe sampled or requested durations as requests unless the effective change has been verified; successful actions can retain or limit existing state.
  14. For UI requests, use Building UI from user requests to map the intended experience to supported controls. Make simple presentation choices, state material assumptions, and ask only when missing behavior affects correctness or permissions. Supply working handlers and state flow, not invented UI widgets.

Suggested request to an AI helper:

Use the attached PLE_CODE_AI_MANUAL.md as the language reference.
Modify the PLE source below to implement: [desired behavior].
Preserve existing names and identities. Return the complete pasteable PLE source,
explain assumptions, and list the events and inputs I should simulate.
Do not use JavaScript APIs, imports, or syntax outside this manual.

[existing PLE source]

Choose the right output​

"Show a message" is incomplete without a destination. For "show in chat", confirm or state that you mean the active Coach chat. This API does not send arbitrary direct messages or community-chat messages.

Intended resultAPI / optionsWhere it appears and what it means
Visible message in Coach chatactions.coachMessageSend({ preferredCoach: "ai", senderRole: "wearer", message: "..." })Displays the supplied text as a relayed wearer message in the selected active Coach chat and asks the Coach to respond. Use "keyholder" only for intended keyholder attribution. Requires an active Coach; delivery can fail.
Internal instruction to a Coachactions.coachMessageSend({ preferredCoach: "ai", senderRole: "programmable_lock", message: "..." })An internal nudge. The supplied text is not displayed as a chat message. A Coach response is separate and must not be confused with displaying your exact text.
Text in the PLE program outputprint("...") or ui.text({ text: "..." })Adds text to PLE UI output. Does not send a chat message, notification, or console message.
Temporary PLE UI feedbackui.toast({ toastType: "info", title: "...", message: "..." })Temporary feedback when the PLE UI renders the run's output. Does not notify absent users or send coach chat.
In-app notificationactions.notify({ target: "wearer", title: "...", message: "..." })Sends an extension notification to the wearer, keyholder, or both. The separate showOverlay field uses the strings "true" / "false". Do not promise device push delivery or coach chat.
Lock-history entryactions.log({ title: "...", description: "..." })Records a program outcome in the lock log, subject to history visibility. Does not send a coach message.

For both Coach modes, preferredCoach: "ai" prefers AI Coach and "bot" prefers Bot Coach. An unavailable preferred Coach can fall back to the other active Coach. A delivery failure is not a promise of a retry through the other Coach. See coach-message delivery and failures and the complete visible-chat example.

Building UI from user requests​

Users describe outcomes such as "make a dashboard", "ask me first", or "give me a menu". Translate those into the supported controls below. Choose a practical interpretation, briefly explain it, and supply a complete program with triggers and callbacks. A vague visual request is not permission to invent an API or add lock-changing actions.

Choose blocks by intent​

What the user asks forUseful building blocksHow to use them in a program
"A button to do something"buttonDeclare it at the top level; put the action inside its handler. Choose the intended role. Use tooltip for instructions and expression inputs active / visible when availability depends on program state.
"A dashboard", "show my progress", "a status panel"ui.text, print, text, variables, and optionally a PLE functionA Status button prints a snapshot of current values. Reuse a reporting function from other handlers when needed. Heading-like text and separator characters are ordinary text; this builds a status readout with separate controls.
"Tell me it worked", "show an error"ui.toastPut temporary feedback after the action succeeds or in the appropriate branch. Use the literal toastType value "info", "success", "warning", or "error". For feedback that should remain readable in program output, use ui.text.
"Ask me first", "are you sure?"ui.confirmPut the change in onConfirm. Put cancellation feedback in onCancel, or omit that callback. Keep the confirmation before effects that must wait for the decision.
"Show details", "a help popup", "preview"ui.modalSupply title, a plain-text message, and buttonText. Add close/dismiss callbacks only if those outcomes need actions. Use ui.text instead when explicit line breaks must be preserved.
"Ask for a note", "remember my answer"ui.input and a string variableDeclare the variable once at the top level and pass its identifier as var. Supply its current value as defaultValue when editing. A separate Status/Show answer control can display the stored value.
"Let me pick an action", "an options menu"ui.actionSheetSet optionCount, supply matching optionNLabel values and onOptionN callbacks, and place the menu in a button handler. Each choice can report status, open a dialog, or perform the requested action.
"A small popup menu"ui.popoverUse optionCount, optionNLabel, and onOptionN branches. Its option table differs from ui.actionSheet: do not copy title, message, or buttonText into this call.
"A wheel that chooses an option"ui.spinWheelSupply 2–12 options with labels, colors, and branches. Use this when an event should open a wheel; see the wheel example for the separate top-level on.spinWheel control. A choice menu selects the user's choice; a wheel selects a random outcome.

For menu and wheel options, N above is explanatory notation. Write actual properties such as option1Label and onOption1, and match them to optionCount; do not generate them using JavaScript loops or object spreads. Read each API's full option table before using it.

Resolve vague requests without inventing features​

  1. Identify who uses the controls, what triggers them, what the user sees, and what state changes. For a read-only personal status view, a wearer-only Status button is a reasonable explicit assumption. Ask a focused question when missing intent changes permissions, recipients, or lock effects.
  2. Separate presentation choices from behavior. Pick simple labels and readable messages when unspecified. Do not silently add a cooldown, reset stored data, choose a penalty amount, or introduce a reward just to make the UI more elaborate.
  3. Translate a requested visual element into its supported equivalent. For example: "I'll use a status readout and separate wearer buttons for your dashboard." A menu can offer related actions. There are no card, grid, panel, tab, heading-style, separator, slider, or multi-field form APIs in this reference; a text label does not create one of those widgets.
  4. Use one input prompt per stored answer. For a request such as "edit my profile", identify the specific answers needed and use supported string variables and input controls. Do not invent fields, onSubmit, onChange, numeric-input widgets, or an HTML form. Ask when the intended fields are essential and unspecified.
  5. Explain where output appears. PLE output, a toast, a modal, Coach chat, notifications, and lock history are different destinations. For example, a background event producing UI output does not establish delivery to an absent user; use the output comparison when notification is intended.
  6. If a specifically required widget has no suitable equivalent, say which part is unsupported and offer a concrete supported design. Do not claim that a dash line is a styled divider or that separate buttons are inside a modal.

State and interaction rules​

  • Top-level button(...) declarations define controls. Put ui.* action calls inside an event handler or a called PLE function; detached top-level action blocks do not run automatically. Nest callbacks to define behavior after a choice, not to define nested visual controls.
  • Declare shared state with top-level let. Use a PLE function for repeated reporting or actions. A function has no automatic UI effect: invoke it with functions.call. Inside a typed function, read its parameters with functions.parameter as described in functions and parameters.
  • Treat a status readout as a snapshot generated when its handler runs. Variables persist between executions, but printed text is not a JavaScript binding that updates itself whenever a value changes. Call the reporting function after relevant changes or expose a Status button.
  • Put confirmation and other decision UI before mutations, random draws, messages, or scheduling that must wait for the user's response. In a menu branch that opens another decision dialog, put the dialog before those effects too. Earlier steps can be replayed when an interactive decision is submitted; see example 11. Recheck relevant current state in a decision callback if another event can change it while the dialog is open.
  • ui.confirm and ui.modal accept optional onConfirm / onCancel callbacks. ui.input has a different contract: it stores the answer in var and cancellation leaves that variable unchanged. It has no onConfirm, onCancel, or onSubmit options. Do not put a "Saved" message after input and assume it distinguishes submission from cancellation; use a separate readout when that distinction is not available.
  • UI actions do not return JavaScript promises or answers. Do not use await, .then(...), window.confirm, React components, HTML, CSS options, or arbitrary DOM APIs. Use the documented branches and variable selectors. To expose a confirmation answer as a Boolean value, use a returning function.
  • Keep messages literal when fixed. Use text("Completed: ", completed) to combine values; a single string does not need text(...). Follow the UI rendering rules for newlines and plain text.

Complete recipe: a check-in dashboard​

For a request such as "give me a dashboard where I can record check-ins, edit a note, and see my status", this program chooses four wearer-only controls. It stores only PLE check-in state and makes no lock-time or Coach-chat changes. The declarations set the initial count and note; later button clicks use the current stored values. Reopening a dialog does not reset them. Replace the labels and requested behavior as needed while preserving the interaction structure.

let checkInCount: integer = 0;
let latestNote: string = "";

functions.def({ name: "showCheckInStatus", parameters: {} }, () => {
ui.text({ text: "CHECK-IN STATUS" });
ui.text({ text: text("Completed check-ins: ", checkInCount) });
ui.text({ text: text("Latest note: ", latestNote) });
});

button("Status", { role: "wearer", tooltip: "Show current check-in state" }, () => {
functions.call({ name: "showCheckInStatus" });
});

button("Add check-in", { role: "wearer" }, () => {
ui.confirm({
title: "Record a check-in?",
message: "Confirm to increase your check-in count by one.",
confirmText: "Record",
cancelText: "Cancel",
onConfirm: () => {
checkInCount += 1;
ui.toast({ toastType: "success", title: "Recorded", message: "Your check-in was recorded." });
functions.call({ name: "showCheckInStatus" });
},
onCancel: () => {
ui.toast({ toastType: "info", title: "Cancelled", message: "Your check-in count was not changed." });
}
});
});

button("Edit note", { role: "wearer" }, () => {
ui.input({
var: latestNote,
title: "Check-in note",
message: "Enter the note to keep in your PLE state.",
placeholder: "Your note",
defaultValue: latestNote,
confirmText: "Save",
cancelText: "Cancel"
});
});

button("More", { role: "wearer" }, () => {
ui.actionSheet({
optionCount: 2,
title: "Check-in actions",
message: "Choose what to display.",
buttonText: "Choose",
option1Label: "Status",
onOption1: () => { functions.call({ name: "showCheckInStatus" }); },
option2Label: "Help",
onOption2: () => {
ui.modal({
title: "Check-in help",
message: "Use Add check-in to record progress, Edit note to update your note, and Status to read the current values.",
buttonText: "Close"
});
}
});
});

The function supplies the same readout from Status, a successful check-in, and the menu. The menu's Help branch opens a modal with no close callbacks because closing it needs no further action. Note input occupies its own handler; use Status afterward to inspect the value instead of guessing whether input was submitted or cancelled.

After applying and compiling, test with the draft simulator's wearer controls:

TestExpected result
Open Status in a fresh simulationCount is 0; the note is empty. Reading status changes no variables.
Add check-in, then CancelAn informational toast; count stays 0.
Add check-in, then RecordCount becomes 1, a success toast appears, and the readout shows 1.
Edit note, save Ready, then open StatusThe stored note is Ready.
Edit note again, type a different value, then CancelStatus still shows Ready.
More, then StatusThe same current values appear without changes.
More, then Help; close or dismiss the modalHelp appears; count and note remain unchanged.

Reset simulation between independent scenarios, not between steps that intentionally depend on prior state. For smaller programs, adapt the UI playground without variables or functions, the input example, or the menu example.

Editing and validation workflow​

  1. Keep a workspace export before a large rewrite. Export/Import as text concerns Blockly workspace JSON; PLE source belongs in the experimental Code editor.
  2. Open Code, copy the current source to your helper, and describe the change. Existing generated metadata may be necessary to preserve Blockly state.
  3. Paste the result, then apply it or switch to Blockly. Parsing and Blockly connection validation must succeed before the existing workspace is replaced. An error leaves the previous workspace available.
  4. Compile and inspect warnings. Use the event's Play icon or the existing Test workflow with suitable mock event inputs. Branching UI examples also need their choices exercised. Play in the draft editor uses simulation, not live lock actions.
  5. Switch to Blockly to inspect connections, then reopen Code. Formatting and helper spellings can normalize. Save the configuration through the normal editor flow when satisfied.

Applying code is not the same as saving the configuration. Text comments and formatting are not retained through Blockly regeneration. Matching blocks retain their existing identities, layout, and Blockly notes where possible. Large reorderings or ambiguous rewrites can create new identities; exact textual round trips are not guaranteed.

Hover over a helper or block call, an option name, a declared variable, or an event field to read inline help. Calls reuse the visual block's instructions; variables show their declared type and initial value when available, not their current runtime value. Event-field help follows the enclosing handler. Hovering a declared function's quoted name in functions.def or functions.call also shows its parameter signature. Press Escape to dismiss hover help, or click/tap a line's ? to open persistent block instructions.

For example, in on.taskCompleted(task => { print(task.taskTitle); });, hover over print for its block instructions or task.taskTitle for the event-field description. Hovering over code does not apply, compile, or run it.

Testing randomness and messages​

Draft Play simulates actions and reports their output; it does not freeze or unlock a real lock, post a real chat message, or prove that live Coach delivery will succeed. Simulator state persists between runs until Reset simulation. Reset between independent scenarios, particularly after a simulated unlock or freeze, so earlier effects do not change the next test's starting state.

Test both branches of a random condition. In a temporary draft, change chance to 100 to exercise the true branch and 0 to exercise the false branch, then restore the intended percentage before saving. A 10% chance is a new random decision each time, not a guarantee of one win in ten clicks; a few repeated runs do not validate the percentage. For messages, inspect the selected action, sender role, and resolved text in the draft results. Actual visibility, active-Coach availability, and delivery failures require a separate live check with the intended configuration.

Execution, failures, and requested effects​

Order and failures​

Statements in an event or branch execute in order. When an action reports a failure, the current execution halts and subsequent steps in that path do not run. Already-applied action effects are not rolled back. For example, if a time change succeeds and the following Coach message fails, the time change remains; a later print(...) is not reached. A failure is different from an explicitly documented successful no-op.

PLE action calls are statements, not JavaScript result objects. Do not write if (actions.coachMessageSend(...)), assign its return value, or invent an ignoreErrors option. There is no try/catch syntax. Check documented prerequisites before depending on an action and explain the consequence of placing optional messaging before an essential change: a messaging failure prevents that later change. Placing messaging after the change preserves that ordering, but cannot undo the change if delivery fails.

Interactive runs and simulated webhooks have an end-to-end deadline (15 seconds by default), including Coach/provider waits and nested Coach calls. On timeout, the server cancels the provider request and stops subsequent program steps, then finishes execution cleanup before returning runtime_timeout (HTTP 504). An action already in progress may finish during cleanup; completed effects are not rolled back, so retrying is not an automatic rollback or a guarantee against repeating earlier actions. A dialog pauses the run; submitting it starts a new request with its own deadline.

actions.endLock() ends the lock session but is not an implicit return from the handler. On success, following statements are still attempted. Their own prerequisites can fail because the lock has ended or a required service is unavailable. A later messaging failure does not relock the ended session. Ending a session also does not promise that another future event will run for that ended lock.

Requested amounts and freeze choices​

A sampled duration is the amount requested, not proof of the exact effect. Store a random duration in a variable once when the action and its message must use the same draw. values.randomDuration(...) returns seconds; use unit: "SECOND" when passing that stored value to a generic duration action.

Lock limits and lock type can constrain time changes. Freeze policy can preserve an existing longer or indefinite freeze, and trap mode keeps freezes indefinite. Prefer wording such as "Requested 2 hours of additional time" over "Exactly 2 hours were added" unless the effective change has been verified. Formatting the requested variable with values.formatDuration(...) does not measure the applied change.

  • actions.freeze({ amount: ..., unit: ... }) requests a timed freeze.
  • actions.freezeUntil({ timestamp: ... }) requests a freeze until a date/time in Unix seconds.
  • actions.freezeIndefinitely() requests a freeze without an end time; plan an explicit unfreeze condition or action.
  • actions.unfreeze() removes the freeze.

If a user has not chosen a duration or whether freezing should be indefinite, clarify that choice or state your assumption explicitly. The presence of a Blockly field default does not resolve missing user intent.

Community-chat gag control​

Gag control requires an eligible active shared lock with a trusted keyholder; self locks are excluded. Programs act under the current keyholder's control and affect only their own lock's gag, not another lock's gag. This controls community-chat gagging, not direct messages or Coach chat. Runtime eligibility checks still apply to scheduled events and wearer-triggered buttons; choose a keyholder-only button when wearers must not invoke the action.

Use actions.gag() to enable an indefinite gag, actions.gag({ durationSeconds: duration(10, "minute") }) for a timed gag, and actions.ungag() to disable it. The optional durationSeconds is a Number expression input in seconds; an unconnected input means indefinite, not zero. These statement actions have no return value or callback branches.

The gag condition also recognizes a gag applied by an actively authorized secondary keyholder. The primary’s program can remove that gag. Secondary keyholders use the community-chat controls with separately approved Manage virtual gags permission; this permission does not enable direct secondary PLE gag actions.

The equivalent of a request for is_enabled(gag) is conditions.lockIs({ flag: "GAG_ENABLED" }), a Boolean value compiled as gag_enabled. Do not invent an is_enabled helper.

on.gagStatusChanged({}, (event) => { ... }) runs on gag_status_changed; event.gagEnabled is the new Boolean state (event_gag_enabled, visual event field GAG_ENABLED). Read it only inside this event or pass it to a function. Unlike the event snapshot, the condition reads the current state. No dialog pause/resume is involved.

button("Gag for ten minutes", { role: "keyholder" }, () => {
actions.gag({ durationSeconds: duration(10, "minute") });
});
button("Remove gag", { role: "keyholder" }, () => {
if (conditions.lockIs({ flag: "GAG_ENABLED" })) {
actions.ungag();
}
});
on.gagStatusChanged({}, (event) => {
if (event.gagEnabled) {
print("Community-chat gag enabled");
} else {
print("Community-chat gag disabled");
}
});

Durations must resolve to 1–31,536,000 whole seconds (up to 365 days). Reapplying a gag replaces its deadline; omitting the duration makes it indefinite. Repeating enable/disable without changing the Boolean state does not emit another status event. The condition reads this lock's state loaded for the current execution, updated after its gag actions; timed expiry is checked when evaluated. Another lock may still gag the wearer after this lock is ungagged.

Enabling, disabling, and timed expiry queue status events, normally delivered by the 15-second background sweep. Expiry stops blocking chat at the deadline even before delivery. These background handlers cannot open dialogs. Gag changes made inside a gag-status handler take effect but do not recursively invoke gag-status handlers. Ending the lock, losing trust, or changing the wearer/keyholder clears its gag and pending events; old relationship events are ignored, not replayed against the new relationship.

Blocks and Code share pl_action_gag, pl_action_ungag, pl_condition_lock_is, and pl_event_gag_status_changed. Switching editors preserves the optional duration and event value. Draft tests simulate gag actions and expiry against their test clock; choose the gag-status event explicitly to test its handler.

Language rules​

Program structure​

A program consists of top-level let declarations, event declarations, function definitions, and optionally detached block calls retained from a Blockly workspace. Executable statements belong inside an event, function, or branch callback. A detached action at the top level does not create an event that runs it.

Use semicolons and braces consistently. Line comments (//) and block comments (/* ... */) are accepted but are discarded when source is regenerated. Use ordinary single- or double-quoted strings with escapes, not template literals.

ConstructSupported form / behavior
Workspace declarationlet count: integer = 0;
Eventon.taskCompleted((event) => { ... });
Simple buttonbutton("Label", () => { ... });
Conditionalif (condition) { ... } else if (condition) { ... } else { ... }
Assignmentcount = expression;, count += expression;, count -= expression;
Increment/decrementcount++;, count--; (postfix only)
Numeric operations+, -, *, /, %, ** (% is remainder)
Comparisons==, !=, ===, !==, <, <=, >, >=
Boolean operations&&, ||, !; other logical operations use conditions.logic(...)
GroupingParenthesized expressions
Event propertyevent.taskTitle, using the current callback's parameter name
Block callnamespace.method({ option: value }), optionally with a callback
Loops / functionsUse loops.* and functions.*; no native JS loop/function syntax
Value selectionlogic.ternary({ if: condition, then: value, else: value })

=== and == both become the same PLE comparison; likewise !== and !=. They do not select separate JavaScript strict/coercive execution modes. + is numeric arithmetic; use text(...) for string concatenation. A negative numeric literal is supported; for negating a computed value use 0 - expression or math.single({ op: "NEG", num: expression }).

Callback bodies must be blocks. Event callbacks take zero or one plain parameter; use () => { ... } or (event) => { ... }. Non-event branches and function bodies take no JavaScript parameters. Avoid naming the event parameter after a declared workspace variable. Callback arguments are not typed TypeScript function parameters.

Unsupported JavaScript and TypeScript​

Do not use const, var, local declarations inside handlers, regular function declarations, return, native for/while, break, continue, classes, imports, exports, interfaces, type aliases, generics, assertions, optional chaining, destructuring, spreads, template strings, promises, async/await, try/catch, or arbitrary calls/property access. There is no Math, Date, JSON, console, fetch, browser DOM, filesystem, or npm access.

Arrays and object literals are accepted only where the adapter specifically expects literal data: variable initializers, function parameter schemas, block options, and metadata. They are not general-purpose runtime expression constructors. Use a declared number_list variable as a list expression. There is no array indexing syntax, .push(), .map(), or string .includes(); use the corresponding documented blocks.

null is allowed in literal metadata, but it is not a general value expression. undefined, NaN, Infinity, and bigint are not program values. Number literals must be finite; decimal, exponent, hexadecimal, binary, octal, and valid numeric separators are accepted.

Limits​

The adapter limits source to 262,144 JavaScript string code units, traversal nesting to 80, and visited conversion nodes to 5,000. These are parsing limits, not a promise that a program at those sizes will compile. The existing compiler and runtime impose their own workspace, action, nesting, loop, and execution limits.

There are at most 100 workspace variables, 4 parameters per function, and 50 else if branches in one conditional. Number lists hold at most 100 finite numbers. Numeric repeat loops are limited to 100 iterations by the existing compiler; use bounded work and change the condition inside a while/until loop. Use scheduling for later execution rather than a busy loop.

Variables and identity​

Variables are shared program state, not local JavaScript variables. Their declarations describe initial values; they are not assignments that run at the start of every event. To reset a value on an event, explicitly assign it inside that handler.

TypeDeclaration exampleMeaning
numberlet ratio: number = 1.5;Finite number, including fractions.
integerlet count: integer = 0;Safe whole number.
booleanlet ready: boolean = false;true or false.
stringlet message: string = "";Initial text up to 2,000 characters.
date_timelet when: date_time = 0;Safe integer Unix timestamp in seconds.
colorlet accent: color = "#6688dd";Six-digit #rrggbb color.
number_listlet scores: number_list = [1, 2, 3];Up to 100 finite numbers.
anylet legacy: any;Legacy untyped variable; no initializer is allowed.

Explicit types are recommended. Plain numeric, text, and Boolean initializers can infer number, string, and boolean. Other types need their explicit annotation. Typed declarations need a literal initializer: let when: date_time = currentTime(); is unsupported; declare 0, then assign currentTime() in a handler.

Use identifier names beginning with an ASCII letter, _, or $, followed by letters, digits, _, or $. Avoid language keywords, helper names, namespaces, and event. If an existing Blockly name is not a valid code identifier, retain it with an alias:

let completedTasks: integer = named("Completed tasks", 0);
let legacyValue: any = named("Legacy value");

on.taskCompleted(() => {
completedTasks++;
legacyValue = completedTasks;
print(text("Completed: ", completedTasks));
});

named("Blockly name", initialValue?) is only an initializer helper. It is not a runtime function. Preserve its first argument when editing an existing program. Changing only the source alias preserves the Blockly name; changing the Blockly name in code can create a new variable identity. Use the Blockly variable editor for intentional renames that must retain persistent state or AI access bindings. Duplicate source identifiers and duplicate underlying Blockly names are rejected.

Variable selector fields such as ui.input({ var: answer, ... }) and loops.forEach({ var: item, ... }) take the declared identifier, not a quoted name or ID. Direct numeric shorthand checks accept number, integer, and any variables; a date_time variable can therefore fail in a compact helper's numeric argument even though Blockly treats it as Number. For calendar start times, use a date helper directly or a guarded value such as logic.ternary({ if: when > 0, then: when, else: currentTime() }). This also survives regeneration to the compact calendar helper.

Owner-managed group variables​

Group variables are shared between manually connected wearers, with separate values in each owner-created group. They are not declared with let and are not workspace memory. The owner creates variables and assigns wearers in Group variables → Manage global variable groups. Code cannot create a group, assign a wearer, or override a disabled connection. Each wearer has one group; group members must use locks from the same original template owner. Workspace import/export does not transfer memberships or values.

Use the literal key configured by the owner: globals.get({ key: "score" }), globals.connected({ key: "score" }), globals.connectedCount({ key: "score" }), globals.set({ key: "score", value: 10 }), or globals.increment({ key: "score", value: 1 }). Keys start with an ASCII letter, contain only letters/digits/underscores, and are at most 64 characters. The owner chooses one of the seven typed variable types; legacy any is unsupported. Set/increment are statements, not value-returning functions. Increment accepts Number/Integer variables and atomically applies the delta; do not implement a shared counter as get-then-set.

Guard optional access with globals.connected. Disconnected reads/writes fail rather than returning a default. Automatic values can read group variables, but functions used in those inputs cannot write them. Normal button functions may combine shared writes and dialogs; a paused run fails if the connection changes before resumption.

button("Add group point", () => {
if (globals.connected({ key: "score" })) {
globals.increment({ key: "score", value: 1 });
print(globals.get({ key: "score" }));
} else {
print("Score is disconnected.");
}
});

on.globalVariableChanged((event) => {
if (event.variableName == "score") {
print(text("Group score: ", event.variablePreviousValue, " → ", event.variableValue));
}
});

Change handlers run asynchronously in connected active workspaces, including the source workspace if subscribed. They cannot open interactive dialogs, including dialogs inside called functions. Use event previous/new values to test a crossing; a fresh globals.get can already contain a newer value. Equal-value writes emit no event. Changes are ordered per variable and receiving workspace; handlers should avoid feedback loops. Group limits are 60 actual changes/minute, 100 changes awaiting distribution, 12 event-write chain levels, and 4 KiB/value. Interrupted handlers are not blindly replayed because some effects may already have happened.

Ownership changes disconnect the wearer. Returning to the original owner requires manual group assignment. Disabling a variable blocks reading, writing, and receiving its events; membership or connection edits invalidate that wearer's old queued events and paused group-using runs. Groups keep their values for remaining members. New/manual reconnections default to enabled.

Completed connection changes also invalidate unfinished writes using the old connection: those writes cannot commit a shared value or create a change event. Values committed before disconnection are preserved. A globals.connected check earlier in a run does not guarantee access for a later write.

Single-wearer draft Play uses mock global_variable_<key> values, never live groups. Enable the matching test input with the desired type/value, or leave it unchecked for disconnected. Reset test state when changing these initial inputs. This mode does not generate cross-workspace deliveries; run change handlers separately with mock event values. For automatic sharing and events, use Test with 8 wearers, described below.

globals.connectedCount({ key: "score" }) returns the number of distinct active wearers with that variable enabled in the current group, including the caller. It returns an integer and fails when the caller is disconnected. It does not count workspaces or combine separate groups, even when keys match. No owner-wide variable scope exists.

on.globalVariableConnectionsChanged((event) => {
if (event.variableName == "score") {
print(text("Connected wearers: ", event.variablePreviousConnectedCount, " → ", event.variableConnectedCount));
}
});

This background event covers observed count changes from membership, per-variable checkboxes, owner transfers, and active-lock changes. Only currently enabled recipients receive it, including new joiners. No dialogs are allowed. It reports event.variableName, event.variablePreviousConnectedCount, and event.variableConnectedCount; ordinary on.globalVariableChanged also supplies event.variableConnectedCount. Counts describe the event snapshot; a fresh count read can be newer. Existing groups initialize silently. Concurrent/backlogged changes can coalesce, and unchanged counts do not emit this event. Old value events queued before this feature may have null count metadata.

Single-wearer draft count reads use global_connected_count_<key> (default/minimum 1) alongside an enabled global_variable_<key> input. Test its connection-count handler separately using event mocks. In Test with 8 wearers, counts come from the scenario's actual enabled connections and count events run automatically.

Testing shared programs without extra accounts​

Use Simulator / Test runtime → Test with 8 wearers. This is an editor control, not a PLE code API; do not invent functions to create virtual accounts or groups. All actions remain simulated, even when opened from a live lock's editor.

The scenario starts with eight copies of the current editor program in one empty group. Add the required typed variables manually. Each wearer has separate ordinary variables and simulated lock state. globals.get, set, increment, connected, and connectedCount operate on that wearer's enabled connection to the shared test group. Writes automatically deliver matching on.globalVariableChangedFor and catch-all on.globalVariableChanged handlers; connection, membership, and owner changes automatically deliver on.globalVariableConnectionsChanged to currently enabled wearers, including newly connected ones. Equal-value writes and variable creation do not emit value-change events.

Practical check: load the score example from the eight-wearer walkthrough, create Integer score = 0, run Add one point as Wearer 1, and expect eight value handlers with count 8. Disable Wearer 2's connection: the other seven receive 8 → 7; Wearer 2's guarded accesses report disconnection, unguarded accesses fail, and later changes do not reach it. Re-enable it to receive the 7 → 8 count event without replaying missed value events.

A button may pause in a dialog or a returning function that opens one. Use Answer dialog to continue; other wearers may run while it waits. Its queued events wait too. Changing a connection cancels the paused run and invalidates queued events for that wearer. Group event handlers still reject interactive dialogs. Returning to the original owner requires manual group assignment.

Reload the current editor program for one wearer or all after edits; existing copies do not change automatically. Loading resets those wearers' runtime state but keeps shared values. Reset test state restores variables to their initial values and clears runtime state, queue, and log, keeping programs, groups, and connection choices. The scenario is saved in this browser tab, scoped to the account and editor, and restored after editor closure or page reload. Closing the tab clears it. Restoration validates the state without running queued work or answering dialogs. A storage warning means the current scenario only remains in memory; a failed restore offers Start group test to replace it.

Latest output shows recent output for the selected wearer; View log opens its filtered log. The loaded-handler summary reports value/count handler counts. Routine completion rows are hidden unless Show completion entries is checked; they still count toward the log limit. Click Add variable after entering a key/type/value to create the shared variable. The combined log retains 200 entries within 64 KiB. At most 64 deliveries are processed per request; use Process queued events to continue. The queue allows 512 deliveries and event writes stop after 12 chain levels. Other events and clocks are not advanced automatically. This simulation does not prove live permissions, subscription behavior, database races, queue recovery, or device/service delivery. No language syntax or block API is added by this test mode.

Public voting controls​

Use actions.publicVotingStart() / actions.publicVotingStop() to enable or disable voting. Set vote choices, time effects, requirements, and visibility with the other actions.publicVoting* calls below. They compile to the validated public_voting.configure action and preserve votes, history, valid share tokens, existing cooldowns, card accumulators, and shock settings. Permanent disabling cannot be overridden. A concurrent vote, settings change, or ownership/session change can fail an action with public_voting_state_changed; earlier actions are not rolled back.

Public voting has no separate duration. To stop later, use an existing scheduled/custom event containing actions.publicVotingStop(). Required and auto-close counts are cumulative totals, not additional votes. To request two more, use values.publicVotingTotalVotes() + 2 for both counts. A reached auto-close threshold disables voting immediately; re-enabling at that threshold does not reset the vote count. Required votes are timer-lock only. Enabling does not bypass normal lock-closure rules.

Boolean options below are literal checkbox fields. Numeric inputs accept value expressions; times are seconds and cooldown is whole hours. Missing numeric inputs use the documented defaults. Mutations are unavailable in read-only automatic expressions and Coach-called functions. Ordinary function calls and events are supported; dialogs before an action pause before the mutation. Draft simulation applies the same settings validation, without real voting, links, or vote-event delivery. on.communityVoteReceived remains the actual-vote event.

See the tested two-vote example and setup guide. Never invent timed-round, vote-reset, or shock-configuration APIs for these blocks.

Compact helpers​

These helpers are aliases for existing blocks. They are not a separate runtime API.

HelperAccepted arguments / result
button(title, handler)Title is an expression; handler is a block callback.
button(title, options, handler)Options: role, color, active, visible, cooldown, tooltip. Role is a literal "any", "wearer", or "keyholder"; default "any". Other options are inputs; a literal color must be #rrggbb.
addTime(value, unit?)Add lock time; a plain number/expression uses seconds unless a lowercase unit is supplied.
removeTime(value, unit?)Remove lock time with the same unit rules.
print(value)Add UI text (ui.text). Supply a string expression; use text("Value: ", numberExpression) for numbers. This is not a console message or notification.
text(a, b, ...)Join 2–32 values as text. Zero or one argument is unsupported. For a single string, pass the string directly.
duration(amount, unit)Literal nonnegative safe integer amount; lowercase "second", "minute", "hour", or "day". Produces a duration in seconds.
dateTime(date, time, timezone?)Literal "YYYY-MM-DD", "HH:mm", and "LOCAL" or "UTC"; default local. Produces a timestamp in seconds.
relativeDateTime(dayOffset, hour, minute, timezone?)Numeric expressions for date offset and clock time; default local. Produces a timestamp in seconds.
currentTime(part?, timezone?)Default part "timestamp", default timezone "LOCAL". Other parts: "second", "minute", "hour", "dayOfMonth", "dayOfWeek", "month", "year".
createCalendarEntry(options)Inputs: title, startsAt, allDay, duration, notes, reminderEnabled, wearerWriteProtected, color. Literal creatorRole is "wearer" (default) or "keyholder".

Plain strings do not need a helper. text(...) joins multiple values; it is not a string wrapper or a one-argument conversion function. A string literal, string variable, or string-valued block can be used directly wherever a String input is accepted, including function arguments such as arg1 for a declared string parameter.

Intended textCorrect expressionUnsupported expression
A fixed message"Challenge expired."text("Challenge expired.")
An existing string variablemessagetext(message)
A formatted durationvalues.formatDuration({ value: seconds })text(values.formatDuration({ value: seconds }))
A label followed by a numbertext("Completed: ", completedTasks)text(completedTasks)

For example, if tellWearer declares a string parameter, call functions.call({ name: "tellWearer", arg1: "Challenge expired." });. Use arg1: text("Completed: ", completedTasks) only when joining multiple parts. Check every text(...) call in the final source, including failure and expiry branches; a valid multi-part success message does not make a one-part message valid elsewhere.

Short durations like "30s", "15m", "2h", and "1d" work in addTime, removeTime, compact button cooldowns, and compact calendar-entry durations. They do not work in every generic Number input. Use duration(15, "minute") or raw seconds in generic calls.

A Duration block already carries its unit. The compiler recognizes it in duration-aware inputs. When a value is stored in a numeric variable or used in arithmetic, treat the result as seconds and select "SECOND" for a generic action's unit. For a calculated amount in another unit use values.durationFromNumber({ amount: expression, unit: "MINUTE" }).

LOCAL means the wearer/lock timezone used by PLE, not the AI helper's or editor computer's timezone. It is not an arbitrary IANA timezone string. Date text event fields cannot be passed directly as numeric timestamps; use their corresponding timestamp property where one exists.

The generic equivalents sometimes use different casing or option names: button(..., { role: "wearer" }, ...) versus on.guiButton({ role: "WEARER", ... }, ...); currentTime(..., "UTC") versus values.currentTime({ tz: "UTC", ... }).

Generic block calls​

Every registered block has a namespace and method in the complete reference. The general forms are:

namespace.method({ literalField: literal, valueInput: expression });
namespace.method({ literalField: literal, valueInput: expression }, () => { statements });
namespace.method({ branchName: () => { statements } });
on.eventName({ eventOptions }, (event) => { statements });

The options object is optional when no options are needed. Keys in the main options object must be unquoted identifiers with explicit values: { unit: "SECOND" }. Do not use shorthand properties, computed keys, or spreads. Quoted keys are accepted inside literal data objects such as parameters or $state.

Table entryHow to write it
FieldLiteral fixed configuration: string, number, or Boolean exactly as listed. Expressions and variable identifiers are not accepted unless it is a variable selector.
Variable selectorA previously declared variable identifier; no quotes.
InputA supported value expression, including a literal, variable, event property, arithmetic expression, or another value block.
Branch() => { ... }; event branches may use (event) => { ... }.
do branchEither an object option do: () => { ... } or one trailing callback; use one form.

Other branch names, such as onConfirm, onCancel, onHeads, and onOption1, belong in the options object. An event with no do branch, such as on.coinFlip, does not accept a generic trailing callback.

Dropdown values are their stored values, including casing. A dropdown with options "false" and "true" expects strings. A checkbox with default true expects a Boolean. The adapter checks field values against the actual Blockly field; a value that would be clamped or normalized may be rejected. Respect listed number ranges and precision.

Input types in the tables describe Blockly connection checks. Any means no Blockly type restriction; it does not imply arbitrary JavaScript is supported or that every runtime value is meaningful. Number includes numerical expressions and durations/timestamps; Array refers to supported number lists; Colour is a Blockly color connection type. A block's output type and its runtime behavior are both relevant.

Defaults shown in field tables are newly constructed Blockly field defaults. They are not a promise about compiler fallback behavior when an option is omitted. Toolbox presets may supply additional shadow values. Fill all inputs required by your intended behavior and inspect compiler warnings. Date defaults are created using the editor's current date; do not hard-code a snapshot default just because it appears in the reference.

Resource dropdowns are populated by the editor's actual configuration. Geofence, existing-task, parental-control profile, and app options can vary by lock/device. Use values selected or exported from that workspace. Sentinel values such as "__any_area__" have the listed meaning; they are not IDs to replace at random.

Functions and parameters​

Define a procedure with functions.def. parameters is a literal object mapping names to the same eight variable types. The declaration order defines arg1 through arg4 at call sites. Function and parameter names are strings; parameter names must be nonempty, trimmed, unique, and at most 120 characters. Definition names must be unique.

Read a local parameter with functions.parameter({ name: "seconds" }); set it with functions.parameterSet({ name: "seconds", value: ... }). Do not refer to the parameter as a bare identifier. Parameters are local to the function; workspace variables remain shared. Definitions may appear before or after callers.

Add returnsValue: true and a return input to a definition to make its call a value expression. The final return input is evaluated after the body. Use functions.return({ value: ... }) inside the body to return early; native JavaScript return remains unsupported. An empty return input produces null.

functions.def({ name: "double", parameters: { amount: "integer" }, returnsValue: true, return: functions.parameter({ name: "amount" }) * 2 }, () => {
if (functions.parameter({ name: "amount" }) < 0) {
functions.return({ value: 0 });
}
});
on.manual(() => {
addTime(functions.call({ name: "double", arg1: 7 }) + 1);
});

Returning functions called inside event steps may perform actions and pause for dialogs. Completed operands and function parameters are retained across the pause. Automatically evaluated inputs such as button labels, visibility, cooldowns, and timer thresholds only accept functions that calculate values or change their own parameters; they cannot call functions with actions, dialogs, or workspace-variable writes.

Returning a confirmation answer​

To reuse a confirmation as a Boolean condition, put ui.confirm inside a returning function and use functions.return in its callbacks. The caller waits for the answer before evaluating its condition. functions.return exits the surrounding PLE function, including when it appears inside a UI callback. Do not assign ui.confirm(...) directly to a variable or use native JavaScript return, async, or await.

functions.def({ name: "askPermission", returnsValue: true, return: false }, () => {
ui.confirm({
title: "Continue?",
message: "Do you want to continue?",
confirmText: "Yes",
cancelText: "No",
onConfirm: () => {
functions.return({ value: true });
},
onCancel: () => {
functions.return({ value: false });
}
});
});

button("Test confirmation", () => {
if (functions.call({ name: "askPermission" })) {
print("The function returned true.");
} else {
print("The function returned false.");
}
});

Apply and compile, then click Test confirmation in the draft simulator. Yes prints The function returned true.; run again and choose No to print The function returned false.. The definition's return: false is the fallback if execution reaches the end without an early return. This example changes no lock state. Call this function from event steps, as shown; a dialog function cannot be used in automatically evaluated inputs such as button visibility.

Functions without return values​

functions.def({ name: "showReward", parameters: { seconds: "number", label: "string" } }, () => {
print(text(functions.parameter({ name: "label" }), ": ", functions.parameter({ name: "seconds" })));
});

on.taskCompleted((event) => {
functions.call({ name: "showReward", arg1: event.taskPoints, arg2: event.taskTitle });
});

Pass every declared argument with a compatible value. Forward references are supported; arbitrary undeclared functions are not an alternative to functions.call. Use function parameters to carry event data: a function definition has no lexical access to a caller's event parameter. Recursion is subject to the existing runtime limits and is unsuitable as an unbounded scheduler.

The editor resolves friendly names to Blockly parameter IDs and preserves matched identities during switching. Existing legacy function params and serialized $state signatures remain supported for round trips. Preserve them when maintaining a legacy program; for new code use parameters. Do not supply both parameters and $state on one definition.

Parameter types in conditions and arithmetic​

A parameter's declared type applies when it is read inside its function. The catalog's default Any output describes an unselected parameter block, not every parameter read. A declared boolean parameter can be used directly in if, !, &&, and ||; a declared number or integer parameter can be used in arithmetic and numeric assignments. The adapter preserves the parameter reference and the existing Blockly/compiler type checks.

let totalPoints: integer = 0;
let lastReward: integer = 0;

functions.def({ name: "resetPoints", parameters: { resetTotals: "boolean" } }, () => {
if (functions.parameter({ name: "resetTotals" })) {
totalPoints = 0;
lastReward = 0;
}
});

functions.def({ name: "awardPoints", parameters: { amount: "integer" } }, () => {
totalPoints += functions.parameter({ name: "amount" });
lastReward = functions.parameter({ name: "amount" });
});

on.lockStarted(() => {
functions.call({ name: "resetPoints", arg1: true });
});

button("Award points", { role: "wearer" }, () => {
functions.call({ name: "awardPoints", arg1: math.randomInt({ from: 5, to: 15 }) });
ui.toast({ toastType: "success", title: "Points awarded", message: text("Earned: ", lastReward, ". Total: ", totalPoints) });
});

The call evaluates the random amount once; both reads of amount use that argument value. resetPoints leaves both variables unchanged when called with arg1: false.

For an older editor that rejects a declared Boolean parameter in a native condition with Expected a compatible value or construct: boolean, use if (functions.parameter({ name: "resetTotals" }) == true). That comparison is also valid in the current editor. An any parameter has no known Boolean type; use an explicit comparison that matches the intended meaning instead of assuming JavaScript truthiness. Do not turn numeric assignments into comparisons to silence a Boolean diagnostic: amount > 0 produces a Boolean and discards the reward amount. Diagnose the expression at the reported location.

Dynamic blocks and metadata​

Some block fields change which options exist. Set the count, then supply only options within that count. Indices are not uniform across all APIs.

APIDynamic options
ui.actionSheet, ui.popoveroptionCount 1–12; option1Label … optionNLabel, onOption1 … onOptionN.
ui.spinWheel, on.spinWheeloptionCount 2–12; option1Label, option1Color, onOption1 … through N.
conditions.isOneOfoptionCount 2–12; option0 … optionNminus1, plus value.
text.joinpartCount 2–32; add0 … addNminus1. Compact text(...) manages this automatically.
blocks.ifElseelseIfCount 0–50; elseif0Cond, elseif0Do … through N−1, plus cond, do, else. Native if is easier to write.
on.weeklyScheduletimeCount 1–12; first field is time, then time2 … timeN. day1 is Monday through day7 Sunday. Times are HH:mm.
functions.def, functions.callparameters defines typed sockets arg1 … arg4 in signature order.
blocks.randomOutcomeoutcomeCount 2–50; supply an outcomes callback containing blocks.randomOutcomeOption({ weight: ... }, ...) branches.

The reference lists the default shape and describes the repeated patterns. Changing a count can remove sockets; do not leave stale options for removed branches. Custom-event slots are the strings "1" through "5"; those are supported user-selected slots, not opaque generated IDs. Give each handler a unique name and use the same slot in scheduling/cancellation calls.

Reserved options exist for faithful Blockly representation:

OptionMeaning
$stateLiteral object holding Blockly mutator state. Preserve unfamiliar exported metadata. Prefer documented count fields and friendly function parameters for new code.
$disabledLiteral array of disable-reason strings, commonly ["MANUALLY_DISABLED"].
$enabledLegacy Boolean enabled flag; false preserves a disabled legacy block.

The printer may choose generic calls to retain disabled/incomplete blocks, legacy signatures, or state that compact syntax cannot express. Do not strip those options just to shorten code. Disabled and detached blocks remain editable; they do not necessarily generate executable steps.

Event variables​

Each event callback has a specific set of read-only properties. The per-event tables in the reference list the exact code property, type, raw Blockly key, and meaning. Properties vary by handler: for example on.taskCompleted has event.taskPoints, while on.quizPassed has event.scorePercent. Do not infer names from the visible Blockly label.

Use the callback's actual parameter name, for example (task) => { print(task.taskTitle); }. Event variables describe this occurrence. Assign them to workspace variables to retain data for a later occurrence, or pass them to function parameters for immediate reuse. There is no event.role property for buttons; button role is configuration.

Common event properties​

PropertyTypeRaw Blockly keyMeaning
event.namestringEVENT_NAMERuntime event name, such as task.completed; may be empty when the producer supplies no metadata.
event.timestampintegerEVENT_TIMESTAMPEvent time in Unix seconds; missing context gives 0.
event.triggeredByExtensionbooleanTRIGGERED_BY_EXTENSIONWhether the producer attributes the change to an extension/module.
event.triggeredByKeyholderbooleanTRIGGERED_BY_KEYHOLDERWhether the producer attributes the change to the keyholder.
event.triggeredByWearerbooleanTRIGGERED_BY_WEARERWhether the producer attributes the change to the wearer.
event.triggeringExtensionstringTRIGGERING_EXTENSIONExtension slug or module ID, for example wheel, dice, task, or public_voting; empty when absent.

The code adapter accepts these common properties on all 64 event types. That does not guarantee that every producer populates them. Ordinary wheel-result, wheel-text-action, and dice-rolled Blockly dropdowns expose their event-specific fields; their common code aliases can resolve to defaults. Buttons, schedules, and custom events can also lack actor/event metadata. Do not infer that the wearer acted just because triggeredByKeyholder is false.

Missing text values generally return "", missing numbers 0, and missing Booleans false. The previous/new values of a changed variable retain their underlying value type and can be null when absent. A 0 timestamp should be treated as missing unless your application explicitly expects the Unix epoch.

Timestamp exceptions: event.maxDateTimestamp, event.geofenceEventTimestamp, and event.timerExpiryTimestamp contain Unix milliseconds. Divide them by 1000 before passing them to date/time inputs. Common event.timestamp, task submission timestamps, and calendar start/end timestamps use seconds.

values.eventVar({ key: "RAW_KEY" }) is the lower-level equivalent and exposes all 121 registered raw keys in its dropdown. Prefer scoped event.property access in new handlers. A raw key does not create event data that the producer did not supply. conditions.eventText(...) exposes only its listed subset of text-oriented keys; its value is a literal string field, unlike the expression inputs of conditions.compare.

Events, loops, and feedback​

on.variableChanged runs after an AI Keyholder or Bot Coach changes an exposed variable; it is not a general observer for every assignment in PLE code. Filter event.variableName to react to the intended exposed variable. Lock-property actions can produce further events, so time-change handlers should check the source or a condition before applying more time changes. Custom events have one pending occurrence per slot: rescheduling replaces it. Use an interval/weekly schedule for regular work and avoid unbounded self-scheduling chains.

Worked examples​

Each TypeScript fence in this manual is a complete, independent PLE program. They demonstrate syntax and compile behavior; the feature prerequisites still apply at runtime. Examples use UI text for harmless observable output unless an action is the point of the example.

1. Button, cooldown, counter, and conditional​

let presses: integer = 0;

button("Add five minutes", { role: "wearer", color: "#6688dd", active: true, visible: true, cooldown: "1m", tooltip: "Adds five minutes" }, () => {
presses++;
addTime("5m");
if (presses >= 3) {
print("The button has been used at least three times.");
} else {
print(text("Button uses: ", presses));
}
});

2. Task completion reward using event variables​

This requires task events from the Task Module. Match the task title here; use an existing task ID if titles are not unique.

let completed: integer = 0;

on.taskCompleted((event) => {
if (event.taskTitle == "Exercise" && event.taskPoints > 0) {
completed++;
removeTime(duration(10, "minute"));
print(text("Completed ", event.taskTitle, "; total: ", completed));
}
});

3. React to a coach changing an exposed variable​

Expose goal to the AI Keyholder or Bot Coach through the existing variable-access settings. This handler responds to that coach change; ordinary assignments by a PLE button do not demonstrate this event.

let goal: integer = 3;
let lastGoal: integer = 0;

on.variableChanged((event) => {
if (event.variableName == "goal") {
lastGoal = event.variableValue;
print(text("Goal changed from ", event.variablePreviousValue, " to ", lastGoal));
}
});

4. All variable types and generic date inputs​

let ratio: number = 1.5;
let count: integer = 0;
let enabled: boolean = true;
let label: string = "Check-in";
let scheduledAt: date_time = 0;
let accent: color = "#6688dd";
let scores: number_list = [2, 4, 6];
let legacy: any;

on.manual(() => {
scheduledAt = relativeDateTime(1, 9, 0);
legacy = ratio;
count = values.listStatistic({ op: "SUM", list: scores });
if (enabled) {
calendar.createEntry({ title: label, startsAt: logic.ternary({ if: scheduledAt > 0, then: scheduledAt, else: currentTime() }), duration: duration(30, "minute"), allDay: false, color: accent });
}
print(text("Total: ", count, "; ratio: ", legacy));
});

5. Repeat, while, and list iteration​

List positions start at 1. forEach captures the list at the start and writes each current number to the selected workspace variable.

let index: integer = 0;
let item: number = 0;
let scores: number_list = [3, 6, 9];

on.manual(() => {
loops.repeat({ times: 3 }, () => {
print("Repeat");
});
index = 0;
loops.whileUntil({ mode: "WHILE", cond: index < 2 }, () => {
index++;
print(text("Index: ", index));
});
loops.forEach({ var: item, list: scores }, () => {
print(text("Item: ", item));
});
print(text("First: ", values.listItem({ list: scores, index: 1 })));
});

6. Typed function parameter update​

functions.def({ name: "countDown", parameters: { remaining: "integer" } }, () => {
loops.whileUntil({ mode: "WHILE", cond: functions.parameter({ name: "remaining" }) > 0 }, () => {
print(text("Remaining: ", functions.parameter({ name: "remaining" })));
functions.parameterSet({ name: "remaining", value: functions.parameter({ name: "remaining" }) - 1 });
});
});

button("Count down", () => {
functions.call({ name: "countDown", arg1: 3 });
});

7. Interval and weekly scheduling​

The weekly fields day1 … day7 mean Monday … Sunday. Set all days explicitly when authoring a schedule. Local schedules use wearer/lock time. A missing daylight-saving time is skipped; a repeated time runs once. Weekly schedules are checked once per minute.

on.scheduler({ amount: 1, unit: "HOUR", firstRun: "DELAY" }, () => {
print("Hourly check-in");
});

on.weeklySchedule({ timeCount: 2, time: "09:00", time2: "18:00", timezone: "LOCAL", day1: true, day2: true, day3: true, day4: true, day5: true, day6: false, day7: false }, () => {
print("Weekday check-in");
});

8. Replaceable custom-event reminder and cancellation​

Each click replaces the pending reminder for slot 1. A delay below five seconds becomes five seconds. Schedule-at uses an absolute Unix timestamp; a past date is skipped while any existing schedule remains unchanged.

on.custom({ eventId: "1", eventName: "Reminder" }, () => {
actions.notify({ target: "wearer", showOverlay: "false", title: "Reminder", message: "Your check-in is due." });
});

button("Remind me later", () => {
actions.scheduleCustomEventAfter({ eventId: "1", duration: duration(30, "minute") });
});

button("Cancel reminder", { visible: values.customEventScheduled({ eventId: "1" }) }, () => {
actions.cancelCustomEvent({ eventId: "1" });
});

9. Calendar entry and calendar task​

Each invocation creates new entries. A cooldown reduces accidental repeats. Keyholder attribution requires the appropriate shared-lock/calendar permission; shared calendar tasks require the Task Module. All-day entries use the wearer-local date and ignore duration. A date in this example is illustrative, not an ID or special syntax.

button("Plan tomorrow", { cooldown: "1h" }, () => {
createCalendarEntry({ title: "Exercise", startsAt: relativeDateTime(1, 9, 30), duration: "30m", allDay: false, notes: "Bring water", reminderEnabled: true, wearerWriteProtected: false, color: "#6688dd", creatorRole: "wearer" });
calendar.createTask({ creatorRole: "wearer", title: "Check-in", startsAt: relativeDateTime(1, 10, 0), duration: duration(1, "hour"), allDay: false, notes: "Complete your check-in.", reminderEnabled: true, wearerWriteProtected: false, requiresPhoto: false, photoVerificationMode: "single" });
createCalendarEntry({ title: "Future plan", startsAt: dateTime("2099-12-01", "09:00", "UTC"), duration: 1800 });
});

10. Calendar record using the task submission timestamp​

The submission timestamp preserves the original photo-submission time, even when approval happens later. For tasks without a photo submission, it automatically uses the recorded completion time in task-completed events and the recorded failure time in task-failed events.

on.taskCompleted((event) => {
calendar.createEntry({
title: text("Completed: ", event.taskTitle),
startsAt: event.taskSubmittedTimestamp,
allDay: false,
duration: 60,
notes: event.taskDescription,
reminderEnabled: false
});
});

11. Confirmation and text input​

UI action branches use named callbacks. Text input stores the submitted answer; cancel leaves the selected variable unchanged. These controls require the existing interactive PLE UI flow. Keep confirmation as the first step of its trigger to avoid repeating earlier actions when a decision is submitted; this example uses separate input and confirmation buttons.

let answer: string = "";

button("Check in", () => {
ui.input({ var: answer, title: "Check-in", message: "How did it go?", placeholder: "Your answer", defaultValue: answer, confirmText: "Save", cancelText: "Cancel" });
});

button("Show answer", () => {
ui.confirm({ title: "Continue?", message: "Choose an action.", confirmText: "Yes", cancelText: "No",
onConfirm: () => { print(text("Answer: ", answer)); },
onCancel: () => { print("Cancelled"); }
});
});

12. Dynamic action-sheet branches​

button("Choose", () => {
ui.actionSheet({ optionCount: 3, title: "Choose a message", message: "Pick one option.", buttonText: "Open choices",
option1Label: "One", onOption1: () => { print("One"); },
option2Label: "Two", onOption2: () => { print("Two"); },
option3Label: "Three", onOption3: () => { print("Three"); }
});
});

13. PLE wheel and coin-flip controls​

These are PLE controls with their own branches. on.wheelResult and on.diceRolled instead listen for the ordinary lock modules' outcomes.

on.spinWheel({ role: "WEARER", optionCount: 2, title: "Pick a message", description: "Two equally sized choices", active: true, visible: true, cooldown: duration(1, "minute"),
option1Label: "Hello", option1Color: "#6688dd", onOption1: () => { print("Hello"); },
option2Label: "Welcome", option2Color: "#dd8866", onOption2: () => { print("Welcome"); }
});

on.coinFlip({ role: "ANY", title: "Flip", active: true, visible: true,
onHeads: () => { print("Heads"); },
onTails: () => { print("Tails"); }
});

14. Weighted random outcomes​

Weights are relative: 3 and 1 give probabilities of 75% and 25%. Put the option blocks inside outcomes and their actions inside each option's callback.

on.manual(() => {
blocks.randomOutcome({ outcomeCount: 2, outcomes: () => {
blocks.randomOutcomeOption({ weight: 3 }, () => { print("Common outcome"); });
blocks.randomOutcomeOption({ weight: 1 }, () => { print("Rare outcome"); });
} });
});

15. Math, text, membership, and lock data​

API names are exact: use text.indexof, not text.indexOf; use math.minmax, not Math.min.

let selected: number = 0;

on.manual(() => {
selected = math.randomInt({ from: 1, to: 6 });
if (conditions.isOneOf({ optionCount: 3, value: selected, option0: 2, option1: 4, option2: 6 })) {
print("Even roll");
}
print(text.replace({ from: "world", to: "PLE", text: "Hello world" }));
print(text("Position: ", text.indexof({ end: "FIRST", value: "Hello PLE", find: "PLE" })));
print(text("Remainder: ", selected % 2));
print(values.formatDuration({ value: values.lockGet({ key: "TIME_REMAINING_SECONDS" }) }));
});

16. Quiz result properties​

This listens for an existing quiz result. Assigning a generated quiz uses actions.quizGenerateAssign; its Task Module, generation, and expiry requirements still apply.

on.quizPassed((event) => {
print(text("Score: ", event.scorePercent, "%; correct: ", event.correctAnswers, "/", event.totalQuestions));
});
on.quizFailed((event) => {
print(text("Required: ", event.requiredPassPercent, "%; scored: ", event.scorePercent));
});
on.quizExpired((event) => {
print(text("Quiz expired after ", event.answeredQuestions, " answers."));
});

17. Existing module event data and lists​

Dice result strings and numerical dice lists are different block values. Use the list block for number iteration/statistics.

on.diceRolled((event) => {
print(text("Dice: ", event.diceTypes, "; player: ", event.dicePlayerValues));
print(text("Total: ", values.listStatistic({ op: "SUM", list: values.diceList({ player: "PLAYER" }) })));
});

on.wheelResult((event) => {
print(text(event.wheelOptionName, ": ", event.wheelResultMessage));
});

18. Disabled action retained through switching​

on.manual(() => {
ui.text({ text: "This block is disabled", $disabled: ["MANUALLY_DISABLED"] });
print("This block remains active");
});

19. Convert a millisecond event timestamp​

Geofence event timestamps retain their source's millisecond unit. Date-format and scheduling blocks expect seconds.

on.geofencePenaltyActive((event) => {
if (event.geofenceEventTimestamp > 0) {
print(values.formatTimestamp({ value: event.geofenceEventTimestamp / 1000, format: "DATETIME", tz: "LOCAL" }));
}
});

20. Visible message in Coach chat​

This complete program posts a visible message attributed to the wearer. It requires an active AI Coach or Bot Coach configured for the lock. AI is preferred; an unavailable AI Coach falls back to an active Bot Coach. The wearer-only button and 30-second cooldown are explicit example choices, not requirements or defaults.

button("Send coach check-in", { role: "wearer", cooldown: "30s" }, () => {
actions.coachMessageSend({
preferredCoach: "ai",
senderRole: "wearer",
message: "I am checking in from the Programmable Lock button."
});
print("Coach message sent.");
});

With successful live delivery, the supplied text appears as a relayed wearer message in the selected Coach chat. The Coach response is separate. print(...) adds confirmation to PLE output, not to chat. Do not change senderRole to "programmable_lock" while still promising a visible message; that changes this action into an internal nudge. Choose "keyholder" only when the intended attribution is the keyholder.

With neither Coach active, the send fails with coach_not_active and the confirmation after it does not run. Other delivery failures also stop the subsequent confirmation. This example makes no lock-state changes; if you add a time change before the send, that earlier change remains when messaging fails.

Compile and inspect warnings, then use draft Play to inspect the coach.message.send action, senderRole: "wearer", and resolved text. Draft success does not verify that a live chat message was posted or that a Coach is configured. Check actual chat visibility separately when running the saved program with the intended active Coach.

21. UI playground without variables or custom functions​

This complete program demonstrates plain-text output, a toast, confirmation callbacks, and modal callbacks. The arrow callbacks are event/UI handlers; it declares no PLE variables or custom function blocks. It requires the interactive PLE UI and makes no lock-state or Coach-chat changes.

The four button(...) calls create separate wearer-only controls. Open playground prints introductory text; all four controls are already available before it is pressed. The heading and separator are ordinary ui.text output. Preview displays a plain-text modal; see the UI rendering rules for supported formatting.

// UI playground: separate wearer-only controls and PLE UI output.
button("Open playground", { role: "wearer", color: "#6688dd", active: true, visible: true, tooltip: "Shows the playground introduction" }, () => {
ui.text({ text: "UI PLAYGROUND" });
ui.text({ text: "Use the separate Info, Confirm, and Preview buttons to try the PLE UI controls." });
ui.text({ text: "------------------------------------------------------------" });
});

button("Info", { role: "wearer", color: "#6688dd", active: true, visible: true, tooltip: "Shows an informational toast" }, () => {
ui.toast({ toastType: "info", title: "Information", message: "This is an informational toast from the UI playground." });
});

button("Confirm", { role: "wearer", color: "#6688dd", active: true, visible: true, tooltip: "Opens a confirmation dialog" }, () => {
ui.confirm({
title: "Proceed?",
message: "Do you want to confirm this playground action?",
confirmText: "Confirm",
cancelText: "Cancel",
onConfirm: () => { ui.toast({ toastType: "success", title: "Confirmed", message: "You confirmed the playground action." }); },
onCancel: () => { ui.toast({ toastType: "info", title: "Cancelled", message: "You cancelled the playground action." }); }
});
});

button("Preview", { role: "wearer", color: "#6688dd", active: true, visible: true, tooltip: "Shows a plain-text modal" }, () => {
ui.modal({
title: "Preview",
message: "Playground preview: a plain-text message with no rich-text formatting.",
buttonText: "Close",
onConfirm: () => { ui.text({ text: "Preview modal was closed with its button." }); },
onCancel: () => { ui.text({ text: "Preview modal was dismissed without its button." }); }
});
});

The confirmation and modal are the first actions of their triggers, following the interactive-UI ordering rule in example 11. Closing the modal with Close selects onConfirm; dismissing it without that button selects onCancel.

To test in the draft editor:

  1. Apply the source and compile; inspect any warnings. Use the wearer's controls in the simulator.
  2. Run Open playground and check the three text outputs in the PLE output area.
  3. Run Info and check the informational toast. Toasts are temporary UI feedback.
  4. Run Confirm twice: confirm once and cancel once. Check the success and cancellation toasts respectively.
  5. Run Preview twice: use Close once and dismiss without it once. Check the corresponding text output for each callback.

Troubleshooting​

ProblemWhat to change
Unsupported syntaxRemove ordinary JS/TS constructs; use the documented block API. A highlighted expression is not necessarily accepted.
Not supported in experimental code: text(...)Count the arguments at that exact call site: text(...) requires 2–32 values. Replace text("Fixed message") with "Fixed message"; pass existing string values directly. This applies inside functions.call({ arg1: ... }) too.
Unknown API / optionLook up its exact reference entry. Names are case-sensitive; some builtin names are lowercase after normalization.
Expected dropdown valueCopy a stored enum value exactly, including whether it is a string or Boolean. Do not pass a displayed translated label.
Unknown variableDeclare it at top level. For function parameters use functions.parameter, not a bare name.
Unknown event fieldConsult that event's table. Do not copy properties from an unrelated event or access event outside its callback.
Blocks detach or conversion reports incompatible inputsCheck the socket types. A field literal, value input, statement branch, and variable selector require different forms.
Boolean error on a function parameterCheck its declared type. A boolean parameter is supported directly; older editors can use functions.parameter({ name: "flag" }) == true. See parameter types. Do not change unrelated numeric assignments.
Function argument errorEnsure a matching definition, no more than four parameters, and one compatible argN for every declared parameter.
A schedule/action compiles but does nothingInspect warnings, disabled status, trigger context, module enablement, permissions, and device prerequisites. Detached top-level actions do not execute automatically.
Coach output does not show the supplied text in chatCheck senderRole. "programmable_lock" is an internal nudge; use "wearer" or "keyholder" for the intended visible attribution. Draft Play never posts real chat.
coach_not_active or later steps do not runConfigure an active Coach, or choose a different output API if Coach chat was not intended. A failed send stops the current execution; earlier applied actions remain.
An action ran after endLock()Ending the session does not return from the current handler. Review the order and prerequisites of subsequent steps.
A message reports more time than was appliedThe sampled/requested amount can differ from the effective change. Inspect the resulting state and describe the amount as requested unless verified.
Random tests give unexpected percentages or stateReset simulation between independent scenarios, test true/false branches with temporary 100%/0% chances, and restore the real percentage. Small samples need not match the configured percentage.
Time is wrong by a factor of 60 or 1,000Check seconds versus minutes and Unix seconds versus JS milliseconds. Inspect the action's unit field.
Missing event dataMissing context has defaults. Simulate with explicit mock inputs and test a real occurrence before depending on producer metadata.
Source changes after switchingNormalization is expected. Preserve behavior, names, bindings, and metadata rather than whitespace or comments.
Variable state or AI binding appears newAvoid renaming its underlying Blockly name in code; preserve named(...) and use the Blockly rename workflow.

Complete block API reference​

This catalog records the registered block shapes, field ranges, stored dropdown values, tooltips, and per-handler event mappings from this checkout. Builtin blocks used by PLE are included. Internal Blockly mutator-editor blocks are not callable program APIs.

Namespace index​

Reading an API entry​

“Field default” is the Blockly construction default. Input rows have no field default and require expressions or callbacks. A field’s dropdown labels explain the stored values; write the stored value, not the label. Values for resource selectors may depend on the current workspace. The default branch count does not limit a dynamic block to that many branches; use its documented count range.

on APIs​

on.cageCheckSubmitted​

Blockly: pl_event_cage_check_submitted · Kind: event.

Runs when cage_check.submitted webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.calendarActive​

Blockly: pl_event_calendar_active · Kind: event.

Runs once when one of the wearer's calendar events starts, including recurring and all-day events.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.calendarEventTitlestringCALENDAR_EVENT_TITLETitle of the calendar entry at the time of the event.
event.calendarEventTypestringCALENDAR_EVENT_TYPEName of the calendar entry’s category/type.
event.calendarEventNotesstringCALENDAR_EVENT_NOTESNotes or instructions saved on the calendar entry.
event.calendarEventIdstringCALENDAR_EVENT_IDID of the linked calendar entry. For task review, empty when the task is not linked to a calendar entry.
event.calendarOccurrenceDatestringCALENDAR_OCCURRENCE_DATEDate identifying this calendar occurrence, including one occurrence of a recurring entry. Empty for tasks without a calendar link.
event.calendarStartAtstringCALENDAR_START_ATStart date/time as text. Use start timestamp when a numeric date input is required.
event.calendarStartTimestampintegerCALENDAR_START_TIMESTAMPStart of this calendar occurrence in Unix seconds; suitable for seconds-based date/time inputs.
event.calendarEndAtstringCALENDAR_END_ATEnd date/time as text. Use end timestamp when a numeric date input is required.
event.calendarEndTimestampintegerCALENDAR_END_TIMESTAMPEnd of this calendar occurrence in Unix seconds; suitable for seconds-based date/time inputs.
event.calendarDurationSecondsintegerCALENDAR_DURATION_SECONDSDuration of this calendar occurrence, in seconds.
event.calendarIsAllDaybooleanCALENDAR_IS_ALL_DAYWhether this is an all-day calendar occurrence.

on.calendarChanged​

Blockly: pl_event_calendar_changed · Kind: event.

Runs after an entry's title, notes, dates, times, timezone, recurrence, category or color changes. Event variables contain the saved values. Reminder and completion-progress changes do not trigger it.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.calendarEventTitlestringCALENDAR_EVENT_TITLETitle of the calendar entry at the time of the event.
event.calendarEventTypestringCALENDAR_EVENT_TYPEName of the calendar entry’s category/type.
event.calendarEventNotesstringCALENDAR_EVENT_NOTESNotes or instructions saved on the calendar entry.
event.calendarEventIdstringCALENDAR_EVENT_IDID of the linked calendar entry. For task review, empty when the task is not linked to a calendar entry.
event.calendarOccurrenceDatestringCALENDAR_OCCURRENCE_DATEDate identifying this calendar occurrence, including one occurrence of a recurring entry. Empty for tasks without a calendar link.
event.calendarStartAtstringCALENDAR_START_ATStart date/time as text. Use start timestamp when a numeric date input is required.
event.calendarStartTimestampintegerCALENDAR_START_TIMESTAMPStart of this calendar occurrence in Unix seconds; suitable for seconds-based date/time inputs.
event.calendarEndAtstringCALENDAR_END_ATEnd date/time as text. Use end timestamp when a numeric date input is required.
event.calendarEndTimestampintegerCALENDAR_END_TIMESTAMPEnd of this calendar occurrence in Unix seconds; suitable for seconds-based date/time inputs.
event.calendarDurationSecondsintegerCALENDAR_DURATION_SECONDSDuration of this calendar occurrence, in seconds.
event.calendarIsAllDaybooleanCALENDAR_IS_ALL_DAYWhether this is an all-day calendar occurrence.
event.calendarChangedFieldsstringCALENDAR_CHANGED_FIELDSText listing the fields changed by this edit. It is not a Blockly list.

on.calendarEnded​

Blockly: pl_event_calendar_ended · Kind: event.

Runs when a calendar occurrence reaches its end time, including recurring and all-day events. Uses the updated end time after edits. Deleting an entry does not trigger this event.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.calendarEventTitlestringCALENDAR_EVENT_TITLETitle of the calendar entry at the time of the event.
event.calendarEventTypestringCALENDAR_EVENT_TYPEName of the calendar entry’s category/type.
event.calendarEventNotesstringCALENDAR_EVENT_NOTESNotes or instructions saved on the calendar entry.
event.calendarEventIdstringCALENDAR_EVENT_IDID of the linked calendar entry. For task review, empty when the task is not linked to a calendar entry.
event.calendarOccurrenceDatestringCALENDAR_OCCURRENCE_DATEDate identifying this calendar occurrence, including one occurrence of a recurring entry. Empty for tasks without a calendar link.
event.calendarStartAtstringCALENDAR_START_ATStart date/time as text. Use start timestamp when a numeric date input is required.
event.calendarStartTimestampintegerCALENDAR_START_TIMESTAMPStart of this calendar occurrence in Unix seconds; suitable for seconds-based date/time inputs.
event.calendarEndAtstringCALENDAR_END_ATEnd date/time as text. Use end timestamp when a numeric date input is required.
event.calendarEndTimestampintegerCALENDAR_END_TIMESTAMPEnd of this calendar occurrence in Unix seconds; suitable for seconds-based date/time inputs.
event.calendarDurationSecondsintegerCALENDAR_DURATION_SECONDSDuration of this calendar occurrence, in seconds.
event.calendarIsAllDaybooleanCALENDAR_IS_ALL_DAYWhether this is an all-day calendar occurrence.

on.coachDisabled​

Blockly: pl_event_coach_disabled · Kind: event.

Runs when the AI Coach or Bot Coach is disabled for this lock.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.coachTypestringCOACH_TYPEIdentifies the coach whose enabled state changed.
event.coachNamestringCOACH_NAMEDisplay name of that coach.
event.coachEnabledbooleanCOACH_ENABLEDEnabled state reported after the coach change.

on.coachEnabled​

Blockly: pl_event_coach_enabled · Kind: event.

Runs when the AI Coach or Bot Coach is enabled for this lock.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.coachTypestringCOACH_TYPEIdentifies the coach whose enabled state changed.
event.coachNamestringCOACH_NAMEDisplay name of that coach.
event.coachEnabledbooleanCOACH_ENABLEDEnabled state reported after the coach change.

on.coinFlip​

Blockly: pl_event_coin_flip · Kind: event.

Shows an animated coin in the UI. Press it for a fair, server-selected heads or tails result and run that outcome. Use a Duration value to set a cooldown. Visible defaults to true when unconnected. Connect false or a condition to hide the coin; active controls whether it can be used while shown.

Put callbacks in onHeads and onTails. This block has no trailing do callback.

OptionKindField default / input typeAllowed values or usage
roleField: string"ANY""ANY" — any; "WEARER" — wearer; "KEYHOLDER" — keyholder.
titleInputStringSupported value expression.
activeInputAnySupported value expression.
visibleInputBooleanSupported value expression.
cooldownInputNumberSupported value expression.
tooltipInputStringSupported value expression.
onHeadsBranch(event) => { ... } or () => { ... }Named branch in the options object.
onTailsBranch(event) => { ... } or () => { ... }Named branch in the options object.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.communityVoteReceived​

Blockly: pl_event_community_vote_received · Kind: event.

Runs after an add, remove, or random Public Voting vote is saved, even when a limit prevents a time change. Event values include vote type, total votes, requested and applied seconds, and cards changed. Pillory votes have a separate event.

Public Voting settings must keep at least one of Add, Remove, or Random enabled. Disabled vote types are rejected before a vote is saved and do not trigger this event. Random-only voting is supported.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.voteTypestringVOTE_TYPEVote choice: add, remove, or random.
event.voteTotalCountintegerVOTE_TOTAL_COUNTTotal votes reported after this vote.
event.requestedDeltaSecondsintegerREQUESTED_DELTA_SECONDSRequested lock-time change in seconds, before limits or other adjustments.
event.appliedDeltaSecondsintegerAPPLIED_DELTA_SECONDSActual lock-time change in seconds after adjustments. May differ from the requested amount, including zero.
event.voteCardsChangedintegerVOTE_CARDS_CHANGEDCards changed by this vote. For pillory this represents cards added.

on.custom​

Blockly: pl_event_custom · Kind: event.

Give this event a unique name, then use that name from the scheduling and cancellation blocks.

Use one of five string slot values and a unique display name. Scheduling actions target the slot, not the display name.

OptionKindField default / input typeAllowed values or usage
eventIdField: string"1""1"; "2"; "3"; "4"; "5".
eventNameField: string"Custom event 1"Literal text.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.dailyVerificationApproved​

Blockly: pl_event_daily_verification_approved · Kind: event.

Runs after a daily photo is approved by the keyholder, community, or automatic approval. Event values include the verification date, id, code, and approval type. Does not include post-hygiene photos.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.verificationIdstringVERIFICATION_IDID of the daily-photo verification being reviewed.
event.verificationDatestringVERIFICATION_DATEDate associated with the daily verification, as date text.
event.verificationCodestringVERIFICATION_CODEVerification code supplied with the submission.
event.verificationApprovedbooleanVERIFICATION_APPROVEDWhether the task-photo review was approved. False does not by itself explain why it was rejected.
event.verificationApprovalTypestringVERIFICATION_APPROVAL_TYPEReview source, such as keyholder, community, or auto, when supplied.
event.verificationRejectionReasonstringVERIFICATION_REJECTION_REASONReason supplied for rejection; empty when no rejection reason is supplied.

on.dailyVerificationRejected​

Blockly: pl_event_daily_verification_rejected · Kind: event.

Runs after a submitted daily photo is rejected by the keyholder or community. Event values include the verification date, id, code, approval type, and rejection reason. Missing photos and post-hygiene photos do not run this event.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.verificationIdstringVERIFICATION_IDID of the daily-photo verification being reviewed.
event.verificationDatestringVERIFICATION_DATEDate associated with the daily verification, as date text.
event.verificationCodestringVERIFICATION_CODEVerification code supplied with the submission.
event.verificationApprovedbooleanVERIFICATION_APPROVEDWhether the task-photo review was approved. False does not by itself explain why it was rejected.
event.verificationApprovalTypestringVERIFICATION_APPROVAL_TYPEReview source, such as keyholder, community, or auto, when supplied.
event.verificationRejectionReasonstringVERIFICATION_REJECTION_REASONReason supplied for rejection; empty when no rejection reason is supplied.

on.deviceShocked​

Blockly: pl_event_device_shocked · Kind: event.

Secondary-keyholder device commands are excluded from this trigger. Their permission to send a bounded shock does not grant authority to execute the primary keyholder's program or extension actions.

Runs when a shock command is successfully sent to the wearer's device.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.shockSuccessbooleanSHOCK_SUCCESSWhether the device command was reported as successfully sent. This is not independent confirmation of physical delivery.
event.shockStrengthPercentintegerSHOCK_STRENGTH_PERCENTReported shock intensity as a percentage.
event.shockDurationSecondsintegerSHOCK_DURATION_SECONDSReported shock duration in seconds.

on.diceRolled​

Blockly: pl_event_dice_rolled · Kind: event.

Runs when dice.rolled webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties (producer metadata may be absent; use the event-specific fields below).

PropertyTypeRaw Blockly keyDescription
event.diceCountintegerDICE_COUNTTotal number of dice in the roll.
event.diceTypesstringDICE_TYPESText describing the dice types used in this roll.
event.dicePlayerValuesstringDICE_PLAYER_VALUESPlayer roll values as comma-separated text. Use rolled dice for a Number list instead.
event.diceBotValuesstringDICE_BOT_VALUESBot roll values as comma-separated text. Use rolled dice for a Number list instead.
event.diceD4CountintegerDICE_D4_COUNTNumber of d4 dice used in this roll.
event.diceD6CountintegerDICE_D6_COUNTNumber of d6 dice used in this roll.
event.diceD8CountintegerDICE_D8_COUNTNumber of d8 dice used in this roll.
event.diceD10CountintegerDICE_D10_COUNTNumber of d10 dice used in this roll.
event.diceD12CountintegerDICE_D12_COUNTNumber of d12 dice used in this roll.
event.diceD20CountintegerDICE_D20_COUNTNumber of d20 dice used in this roll.
event.diceBotScorenumberDICE_BOT_SCOREBot’s total dice score reported for this roll.
event.diceUserScorenumberDICE_USER_SCOREPlayer’s total dice score reported for this roll.
event.diceAppliedAdjustmentSecondsintegerDICE_APPLIED_ADJUSTMENT_SECONDSApplied lock-time adjustment from the wheel result, in seconds.

on.enterGeofenceAllowedArea​

Blockly: pl_event_enter_geofence_allowed_area · Kind: event.

Runs once when the wearer moves from a disallowed geofence area back into an allowed area.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.geofenceEventTimestampintegerGEOFENCE_EVENT_TIMESTAMPTime of the geofence event in Unix milliseconds. Divide by 1000 for seconds-based date/time inputs.
event.geofenceCurrentlyAllowedbooleanGEOFENCE_CURRENTLY_ALLOWEDWhether the reported location is in an allowed geofence area.

on.enterGeofenceZone​

Blockly: pl_event_enter_geofence_zone · Kind: event.

Runs when the wearer enters any area, a zone, a corridor, or one selected geofence area.

areaId is populated from the current geofence configuration; "__any_area__" matches any area of the selected kind. Copy specific IDs from the workspace.

OptionKindField default / input typeAllowed values or usage
areaKindField: string"ANY""ANY" — Any area; "ZONE" — Zone; "CORRIDOR" — Corridor.
areaIdField: string"__any_area__""__any_area__" — Any matching area.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.geofenceZoneIdstringGEOFENCE_ZONE_IDID of the area involved in this entry or exit.
event.geofenceZoneNamestringGEOFENCE_ZONE_NAMEDisplay name of the area involved in this entry or exit.
event.geofenceZoneTypestringGEOFENCE_ZONE_TYPEArea type reported by the event, such as a zone or corridor.
event.geofenceEventTimestampintegerGEOFENCE_EVENT_TIMESTAMPTime of the geofence event in Unix milliseconds. Divide by 1000 for seconds-based date/time inputs.
event.geofenceCurrentlyAllowedbooleanGEOFENCE_CURRENTLY_ALLOWEDWhether the reported location is in an allowed geofence area.

on.exitGeofenceAllowedArea​

Blockly: pl_event_exit_geofence_allowed_area · Kind: event.

Runs once when the wearer moves from an allowed geofence area into a disallowed area.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.geofenceEventTimestampintegerGEOFENCE_EVENT_TIMESTAMPTime of the geofence event in Unix milliseconds. Divide by 1000 for seconds-based date/time inputs.
event.geofenceCurrentlyAllowedbooleanGEOFENCE_CURRENTLY_ALLOWEDWhether the reported location is in an allowed geofence area.

on.exitGeofenceZone​

Blockly: pl_event_exit_geofence_zone · Kind: event.

Runs when the wearer exits any area, a zone, a corridor, or one selected geofence area.

areaId is populated from the current geofence configuration; "__any_area__" matches any area of the selected kind. Copy specific IDs from the workspace.

OptionKindField default / input typeAllowed values or usage
areaKindField: string"ANY""ANY" — Any area; "ZONE" — Zone; "CORRIDOR" — Corridor.
areaIdField: string"__any_area__""__any_area__" — Any matching area.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.geofenceZoneIdstringGEOFENCE_ZONE_IDID of the area involved in this entry or exit.
event.geofenceZoneNamestringGEOFENCE_ZONE_NAMEDisplay name of the area involved in this entry or exit.
event.geofenceZoneTypestringGEOFENCE_ZONE_TYPEArea type reported by the event, such as a zone or corridor.
event.geofenceEventTimestampintegerGEOFENCE_EVENT_TIMESTAMPTime of the geofence event in Unix milliseconds. Divide by 1000 for seconds-based date/time inputs.
event.geofenceCurrentlyAllowedbooleanGEOFENCE_CURRENTLY_ALLOWEDWhether the reported location is in an allowed geofence area.

on.geofencePenaltyActive​

Blockly: pl_event_geofence_penalty_active · Kind: event.

Runs once when the first geofence penalty is applied after the grace period. Repeated location reports and shock cooldowns do not run it again. Requires a server-validated location report.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.geofenceEventTimestampintegerGEOFENCE_EVENT_TIMESTAMPTime of the geofence event in Unix milliseconds. Divide by 1000 for seconds-based date/time inputs.

on.geofencePenaltyInactive​

Blockly: pl_event_geofence_penalty_inactive · Kind: event.

Runs once when an active geofence penalty episode is cleared, including a safe return, suspension, an inactive schedule, or disabled geofencing. Returning during the grace period does not run it. Added lock time is not undone.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.geofenceEventTimestampintegerGEOFENCE_EVENT_TIMESTAMPTime of the geofence event in Unix milliseconds. Divide by 1000 for seconds-based date/time inputs.

on.globalVariableChanged​

Blockly: pl_event_global_variable_changed · Kind: event.

Runs after an actual shared group-variable change. The event already identifies the changed key and its old/new values; no local copies are required. Filter the key, or use on.globalVariableChangedFor for a dedicated handler; this is distinct from on.variableChanged, which concerns coach edits to exposed workspace variables. Runs in the background with no interactive dialogs. Creation, deletion, membership, and connection changes do not trigger it. Disabled/disconnected recipients receive no events; old events are discarded when their connection changes. See group-variable behavior.

OptionKindUsage
doBranch(event) => { ... }, or a trailing callback.

Accepts the common event properties; actor flags can be absent.

PropertyTypeRaw Blockly keyDescription
event.variableNamestringVARIABLE_NAMEGroup variable key.
event.variablePreviousValuevariesVARIABLE_PREVIOUS_VALUEValue before this change.
event.variableValuevariesVARIABLE_VALUEValue after this change.
event.variableChangeSourcestringVARIABLE_CHANGE_SOURCE"owner" for a management edit, "program" for a PLE write.
event.variableSourceUsernamestringVARIABLE_SOURCE_USERNAMEUsername captured when changed: owner for management edits; originating lock's wearer for program writes, including automatic runs.
event.variableSourceUserIdstringVARIABLE_SOURCE_USER_IDCorresponding stable user ID.
event.variableSourceLockIdstringVARIABLE_SOURCE_LOCK_IDOriginating lock ID for program writes; empty for owner edits.
event.variableConnectedCountintegerVARIABLE_CONNECTED_COUNTEnabled wearer count captured for this value change; null for older queued events.

on.globalVariableChangedFor​

Blockly: pl_event_global_variable_changed_for · Kind: event.

Runs only for changes to the exact, case-sensitive group variable key. Available in Events as when group variable [score] changes. Add separate handlers for each key. The catch-all handler remains available; if both match, both run. All globalVariableChanged event properties, connection checks, asynchronous delivery rules, and background dialog restrictions apply.

OptionKindUsage
keyText fieldDefault "score". Required variable key: a letter followed by letters, digits, or underscores; maximum 64 characters.
doBranch(event) => { ... }, or a trailing callback.
on.globalVariableChangedFor({ key: "score" }, (event) => {
print(text(event.variableSourceUsername, " changed score: ", event.variablePreviousValue, " → ", event.variableValue));
});
on.globalVariableChangedFor({ key: "bonus" }, (event) => {
print(text("New bonus: ", event.variableValue));
});

Single-handler draft Play defaults the mock variable name to the selected key. Override Test inputs → variable name to test a nonmatching key, which skips that handler.

Source identity describes the originating lock's wearer for program writes, not necessarily the human who clicked a control. Each write in an event chain records its own immediate source. Username is a snapshot and may change later; use user ID for comparisons. Older queued events have empty source identity fields, and unavailable users have an empty username. The virtual-wearer tester supplies Wearer 1–Wearer 8, IDs sandbox-wearer-1–sandbox-wearer-8, and lock IDs sandbox-lock-1–sandbox-lock-8; management edits use Owner, sandbox-owner, and an empty lock ID.

on.globalVariableConnectionsChanged​

Blockly: pl_event_global_variable_connections_changed · Kind: event.

Runs asynchronously when the observed number of enabled wearers for a group variable changes. Accepts do: (event) => { ... } or a trailing callback, plus the common event properties. Filter by variable name. It does not change the shared value, cannot open dialogs, and cannot deliver to a disabled/disconnected wearer. See count semantics and recovery.

PropertyTypeRaw Blockly keyDescription
event.variableNamestringVARIABLE_NAMEGroup variable key.
event.variablePreviousConnectedCountintegerVARIABLE_PREVIOUS_CONNECTED_COUNTPreviously observed enabled wearer count.
event.variableConnectedCountintegerVARIABLE_CONNECTED_COUNTNew observed enabled wearer count.

on.guiButton​

Blockly: pl_event_gui_button · Kind: event.

Adds a runtime button trigger in UI mode. Use a Duration value to set a cooldown in seconds, minutes, hours, or days. Visible defaults to true when unconnected. Connect false or a condition to hide this control; active controls whether it can be used while shown.

OptionKindField default / input typeAllowed values or usage
roleField: string"ANY""ANY" — any; "WEARER" — wearer; "KEYHOLDER" — keyholder.
titleInputAnySupported value expression.
colorInputString / ColourSupported value expression.
activeInputAnySupported value expression.
visibleInputBooleanSupported value expression.
cooldownInputNumberSupported value expression.
tooltipInputStringSupported value expression.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.hardcoreModeBelow​

Blockly: pl_event_hardcore_mode_below · Kind: event.

Runs once when timer-based Bot Coach Hardcore Mode crosses from above to at-or-below the supplied duration.

OptionKindField default / input typeAllowed values or usage
durationInputNumberSupported value expression.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerThresholdSecondsintegerTIMER_THRESHOLD_SECONDSThreshold used for this crossing check, in seconds.
event.timerPreviousRemainingSecondsintegerTIMER_PREVIOUS_REMAINING_SECONDSRemaining time at the previous timer check, in seconds.
event.timerCurrentRemainingSecondsintegerTIMER_CURRENT_REMAINING_SECONDSRemaining time at this timer or screen-time check, in seconds.

on.hardcoreModeExpired​

Blockly: pl_event_hardcore_mode_expired · Kind: event.

Runs once when timer-based Bot Coach Hardcore Mode reaches zero.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerPreviousRemainingSecondsintegerTIMER_PREVIOUS_REMAINING_SECONDSRemaining time at the previous timer check, in seconds.
event.timerCurrentRemainingSecondsintegerTIMER_CURRENT_REMAINING_SECONDSRemaining time at this timer or screen-time check, in seconds.
event.timerExpiryTimestampintegerTIMER_EXPIRY_TIMESTAMPTimer expiry as a Unix timestamp in milliseconds. Divide by 1000 for seconds-based date/time inputs.

on.hygieneGrantRevoked​

Blockly: pl_event_hygiene_grant_revoked · Kind: event.

Runs when an available hygienic break grant is revoked. Accepting a break or letting a grant expire does not run this event.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.hygieneWindowSecondsintegerHYGIENE_WINDOW_SECONDSAllowed opening duration for the hygienic break, in seconds.
event.hygienePenaltySecondsintegerHYGIENE_PENALTY_SECONDSPenalty duration reported for the break, in seconds; use actual delta to inspect the applied change.

on.hygieneGranted​

Blockly: pl_event_hygiene_granted · Kind: event.

Runs when permission for a hygienic break is granted. The break starts only when the wearer accepts.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.hygieneWindowSecondsintegerHYGIENE_WINDOW_SECONDSAllowed opening duration for the hygienic break, in seconds.
event.hygienePenaltySecondsintegerHYGIENE_PENALTY_SECONDSPenalty duration reported for the break, in seconds; use actual delta to inspect the applied change.

on.hygieneResumed​

Blockly: pl_event_hygiene_resumed · Kind: event.

Runs when hygiene.resumed webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.hygieneWindowSecondsintegerHYGIENE_WINDOW_SECONDSAllowed opening duration for the hygienic break, in seconds.
event.hygieneUnusedSecondsintegerHYGIENE_UNUSED_SECONDSUnused part of the allowed opening window, in seconds.
event.hygieneUnlockedSecondsintegerHYGIENE_UNLOCKED_SECONDSTime spent unlocked during the break, in seconds.
event.hygienePenaltySecondsintegerHYGIENE_PENALTY_SECONDSPenalty duration reported for the break, in seconds; use actual delta to inspect the applied change.
event.hygieneActualDeltaSecondsintegerHYGIENE_ACTUAL_DELTA_SECONDSActual lock-time adjustment from the hygiene event, in seconds.

on.hygieneResumedLate​

Blockly: pl_event_hygiene_resumed_late · Kind: event.

Runs when hygiene.resumed_late webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.hygieneWindowSecondsintegerHYGIENE_WINDOW_SECONDSAllowed opening duration for the hygienic break, in seconds.
event.hygieneUnusedSecondsintegerHYGIENE_UNUSED_SECONDSUnused part of the allowed opening window, in seconds.
event.hygieneUnlockedSecondsintegerHYGIENE_UNLOCKED_SECONDSTime spent unlocked during the break, in seconds.
event.hygienePenaltySecondsintegerHYGIENE_PENALTY_SECONDSPenalty duration reported for the break, in seconds; use actual delta to inspect the applied change.
event.hygieneLateMinutesnumberHYGIENE_LATE_MINUTESHow late the wearer returned, measured in minutes rather than seconds.
event.hygieneActualDeltaSecondsintegerHYGIENE_ACTUAL_DELTA_SECONDSActual lock-time adjustment from the hygiene event, in seconds.

on.hygieneStarted​

Blockly: pl_event_hygiene_started · Kind: event.

Runs when hygiene.started webhook is received. A hygiene start originating from a secondary-keyholder offer is excluded: accepting that limited offer does not authorize primary-authored automation.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.hygieneWindowSecondsintegerHYGIENE_WINDOW_SECONDSAllowed opening duration for the hygienic break, in seconds.
event.hygienePenaltySecondsintegerHYGIENE_PENALTY_SECONDSPenalty duration reported for the break, in seconds; use actual delta to inspect the applied change.

on.keyholderTrusted​

Blockly: pl_event_keyholder_trusted · Kind: event.

Runs when lock.keyholder_trusted webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.lockArchiveRemoval​

Blockly: pl_event_lock_archive_removal · Kind: event.

Runs when lock.archive_removal webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.archiveSourcestringARCHIVE_SOURCESource reported for the archive or removal operation.
event.archiveActorRolestringARCHIVE_ACTOR_ROLERole of the actor responsible for the archive or removal.
event.archiveLockIdstringARCHIVE_LOCK_IDID of the affected lock.

on.lockFrozen​

Blockly: pl_event_lock_frozen · Kind: event.

Runs when lock.frozen webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.frozenBeforebooleanFROZEN_BEFOREWhether the lock was frozen before this change.
event.frozenAfterbooleanFROZEN_AFTERWhether the lock is frozen after this change.
event.frozenDurationSecondsintegerFROZEN_DURATION_SECONDSFreeze duration supplied by the event, in seconds. Zero alone does not distinguish an indefinite freeze from an omitted duration.

on.lockStarted​

Blockly: pl_event_lock_started · Kind: event.

Runs when lock start webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.lockTimeChanged​

Blockly: pl_event_lock_time_changed · Kind: event.

Runs when lock time changes, including task rewards, Jigsaw rewards, and programmable lock actions. Time changes made inside this trigger are saved but do not trigger it again, to prevent loops.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timeChangedSecondsintegerTIME_CHANGED_SECONDSApplied lock-time change in seconds; positive adds time and negative removes time.
event.beforeReleaseAtstringBEFORE_RELEASE_ATRelease date/time before the change, as date text. This is not a numeric Unix timestamp.
event.afterReleaseAtstringAFTER_RELEASE_ATRelease date/time after the change, as date text. This is not a numeric Unix timestamp.
event.requestedDeltaSecondsintegerREQUESTED_DELTA_SECONDSRequested lock-time change in seconds, before limits or other adjustments.
event.appliedDeltaSecondsintegerAPPLIED_DELTA_SECONDSActual lock-time change in seconds after adjustments. May differ from the requested amount, including zero.

on.lockUnfrozen​

Blockly: pl_event_lock_unfrozen · Kind: event.

Runs when lock.unfrozen webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.frozenBeforebooleanFROZEN_BEFOREWhether the lock was frozen before this change.
event.frozenAfterbooleanFROZEN_AFTERWhether the lock is frozen after this change.
event.frozenDurationSecondsintegerFROZEN_DURATION_SECONDSFreeze duration supplied by the event, in seconds. Zero alone does not distinguish an indefinite freeze from an omitted duration.

on.manual​

Blockly: pl_event_manual · Kind: event.

Entry point for the programmable lock test script.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.maxDateIncreased​

Blockly: pl_event_max_date_increased · Kind: event.

Runs when lock.max_date_increased webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.maxDateBeforestringMAX_DATE_BEFOREPrevious maximum release date/time, as date text; empty when not supplied.
event.maxDateStringstringMAX_DATE_STRINGNew maximum release date/time, as date text.
event.maxDateTimestampintegerMAX_DATE_TIMESTAMPNew maximum release date as a Unix timestamp in milliseconds. Divide by 1000 before using seconds-based date/time blocks.
event.maxDateDeltaSecondsintegerMAX_DATE_DELTA_SECONDSChange to the maximum release date, in seconds.

on.parentalControlAppBlocked​

Blockly: pl_event_parental_control_app_blocked · Kind: event.

Runs after a registered parental-control device reports an app block.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.parentalAppNamestringPARENTAL_APP_NAMEDisplay name of the blocked app.
event.parentalAppPackageNamestringPARENTAL_APP_PACKAGE_NAMEPackage identifier of the blocked app; use this for a more specific match than its display name.
event.parentalBlockReasonstringPARENTAL_BLOCK_REASONReason the device reported for blocking the app or website.
event.parentalRuleSourcestringPARENTAL_RULE_SOURCESource of the restriction that caused the block.
event.parentalDeviceIdstringPARENTAL_DEVICE_IDID of the registered parental-control device that reported the block.

on.parentalControlScreenTimeBelow​

Blockly: pl_event_parental_control_screen_time_below · Kind: event.

Runs once when the remaining daily screen-time allowance crosses from above to at-or-below the supplied duration.

OptionKindField default / input typeAllowed values or usage
durationInputNumberSupported value expression.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerThresholdSecondsintegerTIMER_THRESHOLD_SECONDSThreshold used for this crossing check, in seconds.
event.timerPreviousRemainingSecondsintegerTIMER_PREVIOUS_REMAINING_SECONDSRemaining time at the previous timer check, in seconds.
event.timerCurrentRemainingSecondsintegerTIMER_CURRENT_REMAINING_SECONDSRemaining time at this timer or screen-time check, in seconds.
event.screenTimeDailyAllowanceSecondsintegerSCREEN_TIME_DAILY_ALLOWANCE_SECONDSDaily screen-time allowance reported at the check, in seconds.
event.screenTimeUsedSecondsintegerSCREEN_TIME_USED_SECONDSScreen time already used, in seconds.
event.screenTimeExtraSecondsintegerSCREEN_TIME_EXTRA_SECONDSAdditional screen time granted for the day, in seconds.

on.parentalControlScreenTimeExpired​

Blockly: pl_event_parental_control_screen_time_expired · Kind: event.

Runs once when the remaining daily screen-time allowance reaches zero.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.screenTimeDailyAllowanceSecondsintegerSCREEN_TIME_DAILY_ALLOWANCE_SECONDSDaily screen-time allowance reported at the check, in seconds.
event.screenTimeUsedSecondsintegerSCREEN_TIME_USED_SECONDSScreen time already used, in seconds.
event.screenTimeExtraSecondsintegerSCREEN_TIME_EXTRA_SECONDSAdditional screen time granted for the day, in seconds.
event.timerCurrentRemainingSecondsintegerTIMER_CURRENT_REMAINING_SECONDSRemaining time at this timer or screen-time check, in seconds.

on.parentalControlSelfLockBelow​

Blockly: pl_event_parental_control_self_lock_below · Kind: event.

Runs once when the timer-based parental control self-lock crosses from above to at-or-below the supplied duration.

OptionKindField default / input typeAllowed values or usage
durationInputNumberSupported value expression.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerThresholdSecondsintegerTIMER_THRESHOLD_SECONDSThreshold used for this crossing check, in seconds.
event.timerPreviousRemainingSecondsintegerTIMER_PREVIOUS_REMAINING_SECONDSRemaining time at the previous timer check, in seconds.
event.timerCurrentRemainingSecondsintegerTIMER_CURRENT_REMAINING_SECONDSRemaining time at this timer or screen-time check, in seconds.
event.selfLockModestringSELF_LOCK_MODEMode of the parental-control self-lock reported with the timer event.

on.parentalControlSelfLockExpired​

Blockly: pl_event_parental_control_self_lock_expired · Kind: event.

Runs once when the timer-based parental control self-lock reaches zero.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerPreviousRemainingSecondsintegerTIMER_PREVIOUS_REMAINING_SECONDSRemaining time at the previous timer check, in seconds.
event.timerCurrentRemainingSecondsintegerTIMER_CURRENT_REMAINING_SECONDSRemaining time at this timer or screen-time check, in seconds.
event.timerExpiryTimestampintegerTIMER_EXPIRY_TIMESTAMPTimer expiry as a Unix timestamp in milliseconds. Divide by 1000 for seconds-based date/time inputs.
event.selfLockModestringSELF_LOCK_MODEMode of the parental-control self-lock reported with the timer event.

on.parentalControlWebsiteBlocked​

Blockly: pl_event_parental_control_website_blocked · Kind: event.

Runs after a registered parental-control device reports a website block. Only the normalized domain is exposed.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.parentalWebsiteDomainstringPARENTAL_WEBSITE_DOMAINNormalized blocked domain. This is not the full URL, path, or page content.
event.parentalBlockReasonstringPARENTAL_BLOCK_REASONReason the device reported for blocking the app or website.
event.parentalRuleSourcestringPARENTAL_RULE_SOURCESource of the restriction that caused the block.
event.parentalDeviceIdstringPARENTAL_DEVICE_IDID of the registered parental-control device that reported the block.

on.pilloryEntered​

Blockly: pl_event_pillory_entered · Kind: event.

Runs when pillory.entered webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.pilloryReasonstringPILLORY_REASONReason supplied when pillory was started.
event.pilloryDurationSecondsintegerPILLORY_DURATION_SECONDSPillory voting duration supplied by the event, in seconds.

on.pilloryLeft​

Blockly: pl_event_pillory_left · Kind: event.

Runs when pillory.left webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.pilloryTimeAddedSecondsintegerPILLORY_TIME_ADDED_SECONDSAccumulated lock time added during pillory, in seconds.
event.pilloryVoteTimeAddedSecondsintegerPILLORY_VOTE_TIME_ADDED_SECONDSLock time added by the current vote, in seconds, when supplied.

on.pilloryVoteReceived​

Blockly: pl_event_pillory_vote_received · Kind: event.

Runs after a pillory vote is saved, including votes through a public link. Event values include this vote's time, total votes, total pillory vote time, and cards added.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.pilloryVoteTimeAddedSecondsintegerPILLORY_VOTE_TIME_ADDED_SECONDSLock time added by the current vote, in seconds, when supplied.
event.pilloryTimeAddedSecondsintegerPILLORY_TIME_ADDED_SECONDSAccumulated lock time added during pillory, in seconds.
event.voteTotalCountintegerVOTE_TOTAL_COUNTTotal votes reported after this vote.
event.voteCardsChangedintegerVOTE_CARDS_CHANGEDCards changed by this vote. For pillory this represents cards added.

on.quizExpired​

Blockly: pl_event_quiz_expired · Kind: event.

Runs after an assigned quiz result and its outcomes are saved. Event values include quiz status, score, and question counts.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.quizRunIdstringQUIZ_RUN_IDID of this generated quiz assignment. Use it to distinguish separate quiz runs.
event.quizStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.scorePercentnumberQUIZ_SCORE_PERCENTScore for this quiz run, expressed as a percentage.
event.correctAnswersintegerQUIZ_CORRECT_ANSWERSNumber of correct answers in this quiz run.
event.totalQuestionsintegerQUIZ_TOTAL_QUESTIONSTotal number of questions in the quiz.
event.answeredQuestionsintegerQUIZ_ANSWERED_QUESTIONSNumber of questions answered before the result or expiry.
event.requiredPassPercentnumberQUIZ_PASS_PERCENTAGEPercentage required to pass this quiz.

on.quizFailed​

Blockly: pl_event_quiz_failed · Kind: event.

Runs after an assigned quiz result and its outcomes are saved. Event values include quiz status, score, and question counts.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.quizRunIdstringQUIZ_RUN_IDID of this generated quiz assignment. Use it to distinguish separate quiz runs.
event.quizStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.scorePercentnumberQUIZ_SCORE_PERCENTScore for this quiz run, expressed as a percentage.
event.correctAnswersintegerQUIZ_CORRECT_ANSWERSNumber of correct answers in this quiz run.
event.totalQuestionsintegerQUIZ_TOTAL_QUESTIONSTotal number of questions in the quiz.
event.answeredQuestionsintegerQUIZ_ANSWERED_QUESTIONSNumber of questions answered before the result or expiry.
event.requiredPassPercentnumberQUIZ_PASS_PERCENTAGEPercentage required to pass this quiz.

on.quizPassed​

Blockly: pl_event_quiz_passed · Kind: event.

Runs after an assigned quiz result and its outcomes are saved. Event values include quiz status, score, and question counts.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.quizRunIdstringQUIZ_RUN_IDID of this generated quiz assignment. Use it to distinguish separate quiz runs.
event.quizStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.scorePercentnumberQUIZ_SCORE_PERCENTScore for this quiz run, expressed as a percentage.
event.correctAnswersintegerQUIZ_CORRECT_ANSWERSNumber of correct answers in this quiz run.
event.totalQuestionsintegerQUIZ_TOTAL_QUESTIONSTotal number of questions in the quiz.
event.answeredQuestionsintegerQUIZ_ANSWERED_QUESTIONSNumber of questions answered before the result or expiry.
event.requiredPassPercentnumberQUIZ_PASS_PERCENTAGEPercentage required to pass this quiz.

on.scheduler​

Blockly: pl_event_scheduler · Kind: event.

Scheduler event trigger (server-side phase 1).

OptionKindField default / input typeAllowed values or usage
amountField: number10Number; min 1, max 100000, precision 1.
unitField: string"MINUTE""MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
firstRunField: string"START""START" — when lock started; "DELAY" — after delay.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.shareLinkTimeChanged​

Blockly: pl_event_share_link_time_changed · Kind: event.

Runs when share_link.time_changed webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.shareLinkVoterUsernamestringSHARE_LINK_VOTER_USERNAMEVoter name supplied with the event; defaults to Visitor when no name is supplied.
event.shareLinkTimeChangedSecondsintegerSHARE_LINK_TIME_CHANGED_SECONDSApplied lock-time change in seconds; positive adds time and negative removes time.

on.spinWheel​

Blockly: pl_event_spin_wheel · Kind: event.

Adds a GUI runtime button that opens a spin wheel. Provide 2-12 labeled options. If enabled is not connected, it defaults to true. Use a Duration value to set a cooldown in seconds, minutes, hours, or days. Leave cooldown empty for no cooldown. Visible defaults to true when unconnected. Connect false or a condition to hide the wheel; enabled controls whether it can be used while shown.

With optionCount: N, supply option1Label … optionNLabel and onOption1 … onOptionN. Each option also has optionNColor. Callbacks are named options; do not add sockets beyond the selected count.

OptionKindField default / input typeAllowed values or usage
roleField: string"ANY""ANY" — any; "WEARER" — wearer; "KEYHOLDER" — keyholder.
optionCountField: number6Number; min 2, max 12, precision 1.
titleInputAnySupported value expression.
descriptionInputStringSupported value expression.
colorInputString / ColourSupported value expression.
activeInputAnySupported value expression.
visibleInputBooleanSupported value expression.
cooldownInputNumberSupported value expression.
tooltipInputStringSupported value expression.
option1LabelInputStringSupported value expression.
option1ColorInputString / ColourSupported value expression.
onOption1Branch(event) => { ... } or () => { ... }Named branch in the options object.
option2LabelInputStringSupported value expression.
option2ColorInputString / ColourSupported value expression.
onOption2Branch(event) => { ... } or () => { ... }Named branch in the options object.
option3LabelInputStringSupported value expression.
option3ColorInputString / ColourSupported value expression.
onOption3Branch(event) => { ... } or () => { ... }Named branch in the options object.
option4LabelInputStringSupported value expression.
option4ColorInputString / ColourSupported value expression.
onOption4Branch(event) => { ... } or () => { ... }Named branch in the options object.
option5LabelInputStringSupported value expression.
option5ColorInputString / ColourSupported value expression.
onOption5Branch(event) => { ... } or () => { ... }Named branch in the options object.
option6LabelInputStringSupported value expression.
option6ColorInputString / ColourSupported value expression.
onOption6Branch(event) => { ... } or () => { ... }Named branch in the options object.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.taskAssigned​

Blockly: pl_event_task_assigned · Kind: event.

Runs when task.assigned webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.taskTitlestringTASK_TITLETitle or task text supplied with the assignment.
event.taskStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.taskPointsnumberTASK_POINTSTask points supplied with this event; this is not the lock’s total task-point balance.
event.taskDescriptionstringTASK_DESCRIPTIONAdditional task instructions; empty when none are supplied.

on.taskCompleted​

Blockly: pl_event_task_completed · Kind: event.

Runs when task.completed webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.taskTitlestringTASK_TITLETitle or task text supplied with the assignment.
event.taskStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.taskPointsnumberTASK_POINTSTask points supplied with this event; this is not the lock’s total task-point balance.
event.taskDescriptionstringTASK_DESCRIPTIONAdditional task instructions; empty when none are supplied.
event.taskSubmittedTimestampintegerTASK_SUBMITTED_TIMESTAMPOriginal task-photo submission time in Unix seconds, or the recorded completion or failure time for the task’s outcome when no photo submission exists; 0 if no relevant timestamp is available. Photo submissions keep their original time even when reviewed later.

on.taskFailed​

Blockly: pl_event_task_failed · Kind: event.

Runs when task.failed webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.taskTitlestringTASK_TITLETitle or task text supplied with the assignment.
event.taskStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.taskPointsnumberTASK_POINTSTask points supplied with this event; this is not the lock’s total task-point balance.
event.taskDescriptionstringTASK_DESCRIPTIONAdditional task instructions; empty when none are supplied.
event.taskSubmittedTimestampintegerTASK_SUBMITTED_TIMESTAMPOriginal task-photo submission time in Unix seconds, or the recorded completion or failure time for the task’s outcome when no photo submission exists; 0 if no relevant timestamp is available. Photo submissions keep their original time even when reviewed later.

on.taskVerificationApproved​

Blockly: pl_event_task_verification_approved · Kind: event.

Runs when a task's photo verification is approved, including calendar tasks and before/after photos. Approval can occur before the task's minimum duration has elapsed. Event values include task details, approval type, calendar event id, and occurrence date.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskSubmittedTimestampintegerTASK_SUBMITTED_TIMESTAMPOriginal task-photo submission time in Unix seconds, or the recorded completion or failure time for the task’s outcome when no photo submission exists; 0 if no relevant timestamp is available. Photo submissions keep their original time even when reviewed later.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.taskPointsnumberTASK_POINTSTask points supplied with this event; this is not the lock’s total task-point balance.
event.taskTitlestringTASK_TITLETitle or task text supplied with the assignment.
event.taskDescriptionstringTASK_DESCRIPTIONAdditional task instructions; empty when none are supplied.
event.taskVerificationApprovedbooleanTASK_VERIFICATION_APPROVEDWhether the task-photo review was approved. False does not by itself explain why it was rejected.
event.taskApprovalTypestringTASK_APPROVAL_TYPEReview source, such as keyholder, community, auto, or bot, when supplied.
event.taskRejectionReasonstringTASK_REJECTION_REASONReason supplied for rejection; empty when no rejection reason is supplied.
event.calendarEventIdstringCALENDAR_EVENT_IDID of the linked calendar entry. For task review, empty when the task is not linked to a calendar entry.
event.calendarOccurrenceDatestringCALENDAR_OCCURRENCE_DATEDate identifying this calendar occurrence, including one occurrence of a recurring entry. Empty for tasks without a calendar link.

on.taskVerificationRejected​

Blockly: pl_event_task_verification_rejected · Kind: event.

Runs when a task's photo verification is rejected, including calendar tasks and before/after photos. Event values include task details, approval type, rejection reason, calendar event id, and occurrence date.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskSubmittedTimestampintegerTASK_SUBMITTED_TIMESTAMPOriginal task-photo submission time in Unix seconds, or the recorded completion or failure time for the task’s outcome when no photo submission exists; 0 if no relevant timestamp is available. Photo submissions keep their original time even when reviewed later.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.taskPointsnumberTASK_POINTSTask points supplied with this event; this is not the lock’s total task-point balance.
event.taskTitlestringTASK_TITLETitle or task text supplied with the assignment.
event.taskDescriptionstringTASK_DESCRIPTIONAdditional task instructions; empty when none are supplied.
event.taskVerificationApprovedbooleanTASK_VERIFICATION_APPROVEDWhether the task-photo review was approved. False does not by itself explain why it was rejected.
event.taskApprovalTypestringTASK_APPROVAL_TYPEReview source, such as keyholder, community, auto, or bot, when supplied.
event.taskRejectionReasonstringTASK_REJECTION_REASONReason supplied for rejection; empty when no rejection reason is supplied.
event.calendarEventIdstringCALENDAR_EVENT_IDID of the linked calendar entry. For task review, empty when the task is not linked to a calendar entry.
event.calendarOccurrenceDatestringCALENDAR_OCCURRENCE_DATEDate identifying this calendar occurrence, including one occurrence of a recurring entry. Empty for tasks without a calendar link.

on.taskVoteEnded​

Blockly: pl_event_task_vote_ended · Kind: event.

Runs when task.vote_ended webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.taskSubmittedTimestampintegerTASK_SUBMITTED_TIMESTAMPOriginal task-photo submission time in Unix seconds, or the recorded completion or failure time for the task’s outcome when no photo submission exists; 0 if no relevant timestamp is available. Photo submissions keep their original time even when reviewed later.
event.taskRunIdstringTASK_RUN_IDID of this particular task assignment. Save it to match later completion or review events.
event.taskIdstringTASK_IDID of the task definition. Reassigning the same task can produce a different task run ID.
event.taskStatusstringTASK_STATUSStatus reported for this assignment or quiz result.
event.taskPointsnumberTASK_POINTSTask points supplied with this event; this is not the lock’s total task-point balance.
event.taskTitlestringTASK_TITLETitle or task text supplied with the assignment.
event.taskDescriptionstringTASK_DESCRIPTIONAdditional task instructions; empty when none are supplied.
event.taskVerificationApprovedbooleanTASK_VERIFICATION_APPROVEDWhether the task-photo review was approved. False does not by itself explain why it was rejected.
event.taskApprovalTypestringTASK_APPROVAL_TYPEReview source, such as keyholder, community, auto, or bot, when supplied.
event.taskRejectionReasonstringTASK_REJECTION_REASONReason supplied for rejection; empty when no rejection reason is supplied.
event.calendarEventIdstringCALENDAR_EVENT_IDID of the linked calendar entry. For task review, empty when the task is not linked to a calendar entry.
event.calendarOccurrenceDatestringCALENDAR_OCCURRENCE_DATEDate identifying this calendar occurrence, including one occurrence of a recurring entry. Empty for tasks without a calendar link.

on.timerHidden​

Blockly: pl_event_timer_hidden · Kind: event.

Runs when lock.timer_hidden webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerVisibleBeforebooleanTIMER_VISIBLE_BEFOREWhether the timer was visible before the change.
event.timerVisibleAfterbooleanTIMER_VISIBLE_AFTERWhether the timer is visible after the change.

on.timerRevealed​

Blockly: pl_event_timer_revealed · Kind: event.

Runs when lock.timer_revealed webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.timerVisibleBeforebooleanTIMER_VISIBLE_BEFOREWhether the timer was visible before the change.
event.timerVisibleAfterbooleanTIMER_VISIBLE_AFTERWhether the timer is visible after the change.

on.variableChanged​

Blockly: pl_event_variable_changed · Kind: event.

Runs after an AI Keyholder or Bot Coach changes an exposed variable.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.variableNamestringVARIABLE_NAMEName of the workspace variable that the coach changed.
event.variablePreviousValuevariesVARIABLE_PREVIOUS_VALUEValue before the coach’s change. Keeps the original value type, such as number, string, Boolean, or Number list; may be null when absent.
event.variableValuevariesVARIABLE_VALUEValue after the coach’s change. Keeps the changed variable’s value type; it is not always text.
event.variableChangedByCoachbooleanVARIABLE_CHANGED_BY_COACHWhether the change was reported as coming from a coach.
event.variableChangeSourcestringVARIABLE_CHANGE_SOURCESource reported for the variable change; use it to distinguish which coach or integration made the change.

on.verificationPictureSubmitted​

Blockly: pl_event_verification_picture_submitted · Kind: event.

Runs when verification.picture_submitted webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

PropertyTypeRaw Blockly keyDescription
event.verificationCodestringVERIFICATION_CODEVerification code supplied with the submission.

on.weeklySchedule​

Blockly: pl_event_weekly_schedule · Kind: event.

Runs the same actions at each configured time on the selected weekdays. Choose 1–12 times as HH:mm (24-hour time), checked once per minute. Duplicate times run only once. Select all days for a daily schedule. Wearer local uses the wearer or lock timezone, not the editor's timezone. During daylight-saving changes, a missing time is skipped and a repeated time runs once. After downtime, one overdue run is processed before scheduling the next future time.

Set timeCount to expose time, time2 … time12. The first time is named time, not time1. day1 … day7 mean Monday … Sunday. Checkbox options are Booleans.

OptionKindField default / input typeAllowed values or usage
timeCountField: number1Number; min 1, max 12, precision 1.
timezoneField: string"LOCAL""LOCAL" — wearer local; "UTC".
day1Field: booleantrueLiteral Boolean.
day2Field: booleanfalseLiteral Boolean.
day3Field: booleanfalseLiteral Boolean.
day4Field: booleanfalseLiteral Boolean.
day5Field: booleanfalseLiteral Boolean.
day6Field: booleanfalseLiteral Boolean.
day7Field: booleanfalseLiteral Boolean.
timeField: string"09:00"Literal text.
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties.

No additional handler-specific properties.

on.wheelResult​

Blockly: pl_event_wheel_result · Kind: event.

Runs when any normal wheel result is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties (producer metadata may be absent; use the event-specific fields below).

PropertyTypeRaw Blockly keyDescription
event.wheelOptionNamestringWHEEL_OPTION_NAMELabel of the selected Wheel of Fortune option.
event.wheelResultTypestringWHEEL_RESULT_TYPEType of outcome reported by the wheel.
event.wheelResultMessagestringWHEEL_RESULT_MESSAGEDescription or message supplied with the wheel result.
event.wheelTimeChangedSecondsintegerWHEEL_TIME_CHANGED_SECONDSApplied lock-time adjustment from the wheel result, in seconds.
event.wheelOptionIdstringWHEEL_OPTION_IDID of the selected wheel option.

on.wheelTextAction​

Blockly: pl_event_wheel_text_action · Kind: event.

Runs when wheel.text_action webhook is received.

OptionKindField default / input typeAllowed values or usage
doBranch(event) => { ... } or () => { ... }May instead be the trailing callback.

Event variables: accepts the common event properties (producer metadata may be absent; use the event-specific fields below).

PropertyTypeRaw Blockly keyDescription
event.wheelActionTextstringWHEEL_ACTION_TEXTText of the selected wheel text action. Use it to match a particular option.

actions APIs​

actions.addTime​

Blockly: pl_action_add_time · Kind: statement.

Adds the supplied amount using the selected unit. Check the resulting lock state; maximum-time and lock-type rules still apply. A variable containing the requested amount is not a measurement of the time actually added. Describe it as requested unless the effective change has been verified.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

actions.adjustCards​

Blockly: pl_action_adjust_cards · Kind: statement.

Adds or removes cards from the current deck. Use a whole number, variable, or calculation; 0 does nothing. Removal stops at zero and preserves greens needed to finish. Card effects happen when drawn. Deck size limits apply. Unavailable during a pending green-card decision.

OptionKindField default / input typeAllowed values or usage
operationField: string"ADD""ADD" — add; "REMOVE" — remove.
cardTypeField: string"RED""RED" — Red; "PLUS_1_RED" — Red +1; "PLUS_2_RED" — Red +2; "PLUS_3_RED" — Red +3; "YELLOW" — Yellow; "GREEN" — Green; "FREEZE" — Freeze; "STICKY" — Sticky; "DOUBLE_UP" — Double Up; "RESET" — Reset; "SHOCK" — Shock.
amountInputNumberSupported value expression.

actions.adjustDailyRequirement​

Blockly: pl_action_adjust_daily_requirement · Kind: statement.

Changes today's completed progress without changing the configured daily target. Progress cannot go below zero.

OptionKindField default / input typeAllowed values or usage
operationField: string"ADD""ADD" — add to; "REMOVE" — remove from.
requirementField: string"WHEEL""WHEEL" — wheel spin; "DICE" — dice roll; "CHESS" — chess game; "CASINO" — casino game; "AI_CHAT" — AI / Bot chat.
amountInputNumberSupported value expression.

actions.allowUnlocking​

Blockly: pl_action_allow_unlocking · Kind: statement.

Clears this program’s and Control Pass’s manual unlock blocks. Other unlock requirements still apply.

No options; call actions.allowUnlocking().

actions.beestingCustomWaveShock​

Blockly: pl_action_beesting_custom_wave_shock · Kind: statement.

Choose a saved or community wave. It repeats for the chosen duration (1–300 seconds); intensity limits every step. Wearer limits and allowed modes still apply. Private waves must belong to the controlling account. If the wave changes or becomes unavailable, select it again. Requires BeeSting; other devices will not run a replacement shock.

OptionKindField default / input typeAllowed values or usage
waveField: string""Literal text.
durationInputNumberSupported value expression.
intensityInputNumberSupported value expression.

actions.beestingWaveformShock​

Blockly: pl_action_beesting_waveform_shock · Kind: statement.

Starts a Lockink Beesting shock with Wave 1-10. If the selected wave is disabled or the wearer uses another device, the wearer/device default is used and the fallback is recorded in Programmable Lock history.

OptionKindField default / input typeAllowed values or usage
durationTextField: string"30"Literal text.
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes.
intensityTextField: string"50"Literal text.
waveformField: string"1""1"; "2"; "3"; "4"; "5"; "6"; "7"; "8"; "9"; "10".

actions.beestingWaveformShockValues​

Blockly: pl_action_beesting_waveform_shock_values · Kind: statement.

Starts a BeeSting waveform shock using Number inputs. Attach numbers, variables, calculations, or numeric function parameters for duration, intensity, and wave number. Duration is in seconds (1–300); a duration block can supply other units. Intensity is 1–100% and wave number is 1–10. Wearer limits still apply. Disabled waves or other devices use the wearer/device default, with the fallback recorded in Programmable Lock history.

OptionKindField default / input typeAllowed values or usage
durationInputNumberSupported value expression.
intensityInputNumberSupported value expression.
waveformInputNumberSupported value expression.

actions.cageCheckRequest​

Blockly: pl_action_cage_check_request · Kind: statement.

Requests a cage check from the wearer. The duration controls how long they have to submit it. Optional missed penalties can add lock time or send the wearer to pillory.

OptionKindField default / input typeAllowed values or usage
unitField: string"MINUTE""MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
punishmentTypeField: string"add_time""add_time" — add time; "pillory" — send to pillory.
punishmentUnitField: string"MINUTE""MINUTE" — minutes; "HOUR" — hours.
durationInputNumberSupported value expression.
instructionsInputStringSupported value expression.
punishmentDurationInputNumberSupported value expression.

actions.cancelCustomEvent​

Blockly: pl_action_cancel_custom_event · Kind: statement.

Cancels the pending occurrence for the named event. The event block remains available for future schedules. If the declared event has no pending occurrence, cancellation still succeeds; a reset function does not need to check values.customEventScheduled first. The event must still have a matching on.custom handler and a valid active program session. Missing/invalid event or session configuration can fail; having nothing pending is not itself an error.

OptionKindField default / input typeAllowed values or usage
eventIdField: string"1""1" — Custom event 1; "2" — Custom event 2; "3" — Custom event 3; "4" — Custom event 4; "5" — Custom event 5.

actions.coachMessageSend​

Blockly: pl_action_coach_message_send · Kind: statement.

Sends an internal nudge or visible chat message to the preferred active Coach. If that Coach is unavailable, the other active Coach is used.

Visibility is selected by senderRole, not by preferredCoach:

senderRoleDeliveryIs the supplied text displayed in Coach chat?
"programmable_lock" (default)Internal Programmable Lock nudgeNo. The Coach receives the instruction internally; a possible Coach response is separate from displaying this text.
"wearer"Relayed wearer messageYes, on successful delivery, with wearer attribution.
"keyholder"Relayed keyholder messageYes, on successful delivery, with keyholder attribution.

For "show this in Coach chat", explicitly use a visible sender role with the intended attribution. This block has no separate visible Programmable Lock sender mode. It does not send arbitrary user-to-user or community-chat messages. See the complete visible-chat example.

Prerequisites and failures: The lock needs an active, configured AI Coach or Bot Coach. If neither is available, this action fails with coach_not_active; it is not silently skipped. The message must be nonempty after trimming and at most 2,000 JavaScript string code units, otherwise it fails with coach_message_empty or coach_message_too_long. Coach delivery can fail for other reasons too. Do not treat fallback from an unavailable preferred Coach as a guarantee of delivery or a retry after a delivery failure.

A failed send halts the current execution; subsequent steps do not run and already-applied actions are not rolled back. The call can be attempted after actions.endLock(), but delivery is not guaranteed. It asks the selected Coach to process the message; do not describe it as a passive log or promise particular reply wording. Draft Play simulates this action and does not verify real chat visibility or active-Coach configuration.

OptionKindField default / input typeAllowed values or usage
preferredCoachField: string"ai""ai" — AI; "bot" — Bot.
senderRoleField: string"programmable_lock""programmable_lock" — internal nudge, supplied text not visible; "wearer" — visible wearer-attributed message; "keyholder" — visible keyholder-attributed message.
messageInputStringSupported value expression.

actions.contentShieldFilter​

Blockly: pl_action_content_shield_filter · Kind: statement.

Enables or disables an individual Content Shield filter. At least one filter must remain enabled.

OptionKindField default / input typeAllowed values or usage
categoryField: string"FEMALE_GENITALIA_COVERED""FEMALE_GENITALIA_COVERED" — Female genitalia (covered); "FACE_FEMALE" — Female face; "BUTTOCKS_EXPOSED" — Buttocks (exposed); "FEMALE_BREAST_EXPOSED" — Female breast (exposed); "FEMALE_GENITALIA_EXPOSED" — Female genitalia (exposed); "MALE_BREAST_EXPOSED" — Male breast (exposed); "ANUS_EXPOSED" — Anus (exposed); "FEET_EXPOSED" — Feet (exposed); "BELLY_COVERED" — Belly (covered); "FEET_COVERED" — Feet (covered); "ARMPITS_COVERED" — Armpits (covered); "ARMPITS_EXPOSED" — Armpits (exposed); "FACE_MALE" — Male face; "BELLY_EXPOSED" — Belly (exposed); "MALE_GENITALIA_EXPOSED" — Male genitalia (exposed); "ANUS_COVERED" — Anus (covered); "FEMALE_BREAST_COVERED" — Female breast (covered); "BUTTOCKS_COVERED" — Buttocks (covered).
enabledField: string"on""on"; "off".

actions.contentShieldFilterGroups​

Blockly: pl_action_content_shield_filter_groups · Kind: statement.

Enables or disables one or more supported gendered body-part groups. Faces remain separate. Select all three for every supported group; at least one Content Shield filter must remain enabled.

OptionKindField default / input typeAllowed values or usage
femaleExposedField: booleantrueLiteral Boolean.
femaleCoveredField: booleanfalseLiteral Boolean.
maleExposedField: booleanfalseLiteral Boolean.
enabledField: string"on""on"; "off".

actions.contentShieldMode​

Blockly: pl_action_content_shield_mode · Kind: statement.

Changes a Content Shield mode. An active Content Shield subscription is required.

OptionKindField default / input typeAllowed values or usage
modeField: string"a""a" — A - Browser; "b" — B - AI apps; "c" — C - Family VPN.
enabledField: string"on""on"; "off".

actions.deviceStopAll​

Blockly: pl_action_device_stop_all · Kind: statement.

Sends the device stop-all command. Use the separate secondary-accessory stop block when stopping those accessories too.

No options; call actions.deviceStopAll().

actions.endLock​

Blockly: pl_action_end_lock · Kind: statement.

Immediately unlocks the lock and ends the session, like the keyholder's green Unlock button. Remaining time and ordinary unlock requirements do not prevent this action. This does not abandon the lock or remove the keyholder. Draft tests simulate the action without unlocking a real lock.

This does not terminate the current handler. Following statements are attempted after a successful unlock, and their own prerequisites still apply. If a later action fails, the session remains ended; there is no rollback. Do not depend on a later event to finish work for the ended session.

No options; call actions.endLock().

actions.extendFreeze​

Blockly: pl_action_extend_freeze · Kind: statement.

Adds time to an active timed freeze. Does nothing when the lock is not frozen or is frozen indefinitely.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

actions.freeze​

Blockly: pl_action_freeze · Kind: statement.

Freezes for the supplied duration and unit, subject to the lock's freeze policy.

This is a timed-freeze request. Use actions.freezeIndefinitely() for no end time, or actions.freezeUntil(...) for an absolute date/time. An existing longer or indefinite freeze may be kept; trap mode keeps freezes indefinite. Report the requested duration separately from the effective freeze, and do not choose a duration silently when the user only said "freeze".

A pause belonging only to an active hygienic or soft emergency release does not count as an independent indefinite freeze: this action starts its own timed freeze without resetting paused time or card cooldowns. For example, actions.freeze({ amount: 20, unit: "MINUTE" }) followed by re-locking 5 minutes later leaves 15 minutes of freeze. If that freeze expires first, the temporary-release pause remains until re-locking. Unfreeze actions remove the independent freeze without ending that pause. A separate indefinite freeze is still preserved, and actions.extendFreeze(...) still requires an existing timed freeze.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

actions.freezeIndefinitely​

Secondary-keyholder execution requires the Freeze permission and current primary trust. A custom per-action/daily duration cap or accepted maximum deadline prevents an indefinite freeze; use a timed freeze within those bounds. Existing invitations inherit these limits without reinvitation; their old fixed daily freeze default is not a custom cap.

Blockly: pl_action_freeze_indefinitely · Kind: statement.

Freezes the lock without an end time. Use a separate unfreeze action when the required condition is met.

No options; call actions.freezeIndefinitely().

actions.freezeUntil​

Blockly: pl_action_freeze_until · Kind: statement.

Freezes until the selected date/time or Unix timestamp in seconds. The date/time block chooses the timezone. Past dates are skipped. The end must be at least 1 minute away; more than 7 days requires a trusted keyholder or trap mode. An existing longer or independent indefinite freeze is kept. A hygienic or soft emergency release pause follows the overlap rules under actions.freeze above. In trap mode, freezes remain indefinite.

OptionKindField default / input typeAllowed values or usage
timestampInputNumberSupported value expression.

actions.hygienicUnlockGrant​

Blockly: pl_action_hygienic_unlock_grant · Kind: statement.

Gives the wearer time to start a hygienic unlock using the lock's configured unlock window. Grant expiry: 1 minute to 30 days, default 24 hours. An existing pending grant keeps its original expiry.

OptionKindField default / input typeAllowed values or usage
unitField: string"HOUR""HOUR" — hours; "MINUTE" — minutes; "DAY" — days.
expiryInputNumberSupported value expression.

actions.hygienicUnlockRevokeGrants​

Blockly: pl_action_hygienic_unlock_revoke_grants · Kind: statement.

Removes every unused hygienic unlock grant and its notifications for this lock, including keyholder, admin, and program grants. An unlock already started continues. If the wearer is currently accepting a grant, this action fails and can be tried again.

No options; call actions.hygienicUnlockRevokeGrants().

actions.hygienicUnlockSetWindow​

Blockly: pl_action_hygienic_unlock_set_window · Kind: statement.

Sets a duration from 1 minute to 7 days. Future breaks changes the lock's default window; existing grants keep their assigned duration. Active break sets the remaining time from now without changing future breaks. It requires an active break and does not change the grant acceptance deadline.

OptionKindField default / input typeAllowed values or usage
targetField: string"FUTURE""FUTURE" — future breaks; "ACTIVE" — remaining time of active break.
durationInputNumberSupported value expression.

actions.hygienicUnlockStart​

Blockly: pl_action_hygienic_unlock_start · Kind: statement.

Triggers a temporary (hygienic) opening using the lock's configured hygienic settings.

No options; call actions.hygienicUnlockStart().

actions.log​

Blockly: pl_action_log · Kind: statement.

Supply a title, description, optional color, and icon field for a lock log entry. Use it to record a meaningful program outcome.

OptionKindField default / input typeAllowed values or usage
iconField: string""Literal text.
titleInputStringSupported value expression.
descriptionInputStringSupported value expression.
colorInputString / ColourSupported value expression.

actions.notify​

Blockly: pl_action_notify · Kind: statement.

Sends an in-app extension notification.

OptionKindField default / input typeAllowed values or usage
targetField: string"wearer""wearer"; "keyholder"; "both".
showOverlayField: string"false""false"; "true".
titleInputStringSupported value expression.
messageInputStringSupported value expression.

actions.passwordUnlock​

Blockly: pl_action_password_unlock · Kind: statement.

Shows a password prompt and unlocks the lock when the code matches. New codes require 4–12 letters or numbers. Repeated attempts trigger an increasing cooldown.

OptionKindField default / input typeAllowed values or usage
passcodeField: string""Literal text.

actions.pcApplyProfile​

Blockly: pl_action_pc_apply_profile · Kind: statement.

Applies a saved app-restriction profile to the wearer. App rules are merged; saved browser and screen-time settings are applied.

OptionKindField default / input typeAllowed values or usage
profileIdField: string"__no_profile__""__no_profile__" — No saved profiles available.

actions.pcBlockApp​

Blockly: pl_action_pc_block_app · Kind: statement.

Blocks an installed app by app name or package name.

OptionKindField default / input typeAllowed values or usage
appPickerField: string"__choose_installed_app__""__choose_installed_app__" — choose installed app....
appField: string"Instagram"Literal text.

actions.pcBlockSite​

Blockly: pl_action_pc_block_site · Kind: statement.

Blocks a website or domain.

OptionKindField default / input typeAllowed values or usage
siteField: string"reddit.com"Literal text.

actions.pcClearAppLimit​

Blockly: pl_action_pc_clear_app_limit · Kind: statement.

Removes the daily screen-time limit for one app.

OptionKindField default / input typeAllowed values or usage
appPickerField: string"__choose_installed_app__""__choose_installed_app__" — choose installed app....
appField: string"Instagram"Literal text.

actions.pcClearUsageWindow​

Blockly: pl_action_pc_clear_usage_window · Kind: statement.

Disables the allowed-hours restriction.

No options; call actions.pcClearUsageWindow().

actions.pcGrantExtraTime​

Blockly: pl_action_pc_grant_extra_time · Kind: statement.

Adds screen time for today without changing the recurring daily limit.

OptionKindField default / input typeAllowed values or usage
amountField: number30Number; min 0.25, precision 0.25.
unitField: string"MINUTE""MINUTE" — minutes; "HOUR" — hours.

actions.pcRequestTemporaryBypass​

Blockly: pl_action_pc_request_temporary_bypass · Kind: statement.

Sends an approval request to the Android device; it does not begin the bypass automatically.

No options; call actions.pcRequestTemporaryBypass().

actions.pcRestoreSnapshot​

Blockly: pl_action_pc_restore_snapshot · Kind: statement.

Restores the parental-control rules saved before the current self-control lock.

No options; call actions.pcRestoreSnapshot().

actions.pcSelfControlExtend​

Blockly: pl_action_pc_self_control_extend · Kind: statement.

Extends an active timer-based self-control lock.

OptionKindField default / input typeAllowed values or usage
amountField: number1Number; min 1, precision 1.
unitField: string"HOUR""HOUR" — hours; "MINUTE" — minutes; "DAY" — days.

actions.pcSelfControlLockSession​

Blockly: pl_action_pc_self_control_lock_session · Kind: statement.

Makes self-control parental settings unremovable until the current lock session ends.

No options; call actions.pcSelfControlLockSession().

actions.pcSelfControlLockTimer​

Blockly: pl_action_pc_self_control_lock_timer · Kind: statement.

Makes self-control parental settings unremovable for a duration.

OptionKindField default / input typeAllowed values or usage
amountField: number1Number; min 1, precision 1.
unitField: string"HOUR""HOUR" — hours; "MINUTE" — minutes; "DAY" — days.

actions.pcSetActionShock​

Blockly: pl_action_pc_set_action_shock · Kind: statement.

Configures the shock sent when parental control blocks an action.

OptionKindField default / input typeAllowed values or usage
enabledField: string"on""on"; "off".
amountField: number10Number; min 1, precision 1.
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes.
intensityField: number50Number; min 1, max 100, precision 1.

actions.pcSetAppLimit​

Blockly: pl_action_pc_set_app_limit · Kind: statement.

Sets a daily screen-time limit for one app.

OptionKindField default / input typeAllowed values or usage
appPickerField: string"__choose_installed_app__""__choose_installed_app__" — choose installed app....
appField: string"Instagram"Literal text.
amountField: number1Number; min 0.25, precision 0.25.
unitField: string"HOUR""HOUR" — hours; "MINUTE" — minutes.

actions.pcSetDailyLimit​

Blockly: pl_action_pc_set_daily_limit · Kind: statement.

Sets the recurring daily screen-time allowance.

OptionKindField default / input typeAllowed values or usage
amountField: number2Number; min 0, precision 0.25.
unitField: string"HOUR""HOUR" — hours; "MINUTE" — minutes.

actions.pcSetPornFilter​

Blockly: pl_action_pc_set_porn_filter · Kind: statement.

Enables or disables the browser porn filter.

OptionKindField default / input typeAllowed values or usage
enabledField: string"on""on"; "off".

actions.pcSetScreenTime​

Blockly: pl_action_pc_set_screen_time · Kind: statement.

Enables or disables total daily screen-time enforcement.

OptionKindField default / input typeAllowed values or usage
enabledField: string"on""on"; "off".

actions.pcSetSecureFolderBlock​

Blockly: pl_action_pc_set_secure_folder_block · Kind: statement.

Enables or disables Samsung Secure Folder blocking.

OptionKindField default / input typeAllowed values or usage
enabledField: string"on""on"; "off".

actions.pcSetSettingsBlock​

Blockly: pl_action_pc_set_settings_block · Kind: statement.

Enables or disables blocking of the Android Settings app.

OptionKindField default / input typeAllowed values or usage
enabledField: string"on""on"; "off".

actions.pcSetSettingsExceptions​

Blockly: pl_action_pc_set_settings_exceptions · Kind: statement.

Replaces Settings exceptions. Separate multiple entries with commas.

OptionKindField default / input typeAllowed values or usage
exceptionsField: string"wifi, bluetooth"Literal text.

actions.pcSetStrictMode​

Blockly: pl_action_pc_set_strict_mode · Kind: statement.

Enables or disables parental-control Strict Mode.

OptionKindField default / input typeAllowed values or usage
enabledField: string"on""on"; "off".

actions.pcSetUsageWindow​

Blockly: pl_action_pc_set_usage_window · Kind: statement.

Sets the allowed daily usage window using 24-hour HH:MM times.

OptionKindField default / input typeAllowed values or usage
startField: string"08:00"Literal text.
endField: string"20:00"Literal text.

actions.pcUnblockApp​

Blockly: pl_action_pc_unblock_app · Kind: statement.

Unblocks an installed app by app name or package name.

OptionKindField default / input typeAllowed values or usage
appPickerField: string"__choose_installed_app__""__choose_installed_app__" — choose installed app....
appField: string"Instagram"Literal text.

actions.pcUnblockSite​

Blockly: pl_action_pc_unblock_site · Kind: statement.

Removes a website or domain from the block list.

OptionKindField default / input typeAllowed values or usage
siteField: string"reddit.com"Literal text.

actions.pillory​

Blockly: pl_action_pillory · Kind: statement.

Starts pillory for the chosen voting duration. Leave time per vote empty to keep the lock's setting. A supplied value changes the lock's time per vote setting (1 minute to 24 hours). Update current replaces the remaining voting duration from now and preserves existing votes and totals.

When updating an active pillory started with a one-time manual vote rate, omitting timePerVote preserves that override. Supplying timePerVote replaces it and updates the module default. A new pillory without timePerVote uses the module default, not a previous pillory's one-time override. This also applies to actions.pilloryUntil.

OptionKindField default / input typeAllowed values or usage
ifRunningField: string"SKIP""SKIP" — skip; "UPDATE" — update current.
durationInputNumberSupported value expression.
reasonInputStringSupported value expression.
timePerVoteInputNumberSupported value expression.

actions.pilloryAddTime​

Blockly: pl_action_pillory_add_time · Kind: statement.

Adds the supplied duration to the pillory voting period.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

actions.pilloryEnd​

Blockly: pl_action_pillory_end · Kind: statement.

Ends the current pillory period.

No options; call actions.pilloryEnd().

actions.pilloryRemoveTime​

Blockly: pl_action_pillory_remove_time · Kind: statement.

Removes the supplied duration from the pillory voting period.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

actions.pilloryUntil​

Blockly: pl_action_pillory_until · Kind: statement.

Starts pillory until the selected date/time or Unix timestamp in seconds, up to 365 days from now. The date/time block chooses the timezone. Past dates are skipped. Leave time per vote empty to keep the lock's setting; a supplied value changes that setting (1 minute to 24 hours). Update current changes the end time and preserves existing votes and totals.

OptionKindField default / input typeAllowed values or usage
ifRunningField: string"SKIP""SKIP" — skip; "UPDATE" — update current.
timestampInputNumberSupported value expression.
reasonInputStringSupported value expression.
timePerVoteInputNumberSupported value expression.

actions.preventUnlocking​

Blockly: pl_action_prevent_unlocking · Kind: statement.

Adds a manual unlock block with the supplied reason.

OptionKindField default / input typeAllowed values or usage
reasonInputStringSupported value expression.

actions.publicVotingOptions​

Blockly: pl_action_public_voting_options · Kind: statement.

Sets allowed vote choices and the voter cooldown.

OptionKindDefaultConstraint
allowAddBoolean fieldtrueAllow add votes.
allowRemoveBoolean fieldtrueAllow remove votes.
allowRandomBoolean fieldtrueAllow random votes.
cooldownHoursNumber input6Whole hours, 1–168; existing waits are retained.

actions.publicVotingRequirements​

Blockly: pl_action_public_voting_requirements · Kind: statement.

Sets the unlock and auto-close totals together. Existing votes count toward both. If enabled, auto-close must be at least the required total; an already-reached threshold closes voting immediately.

OptionKindDefaultConstraint
requireVotesBoolean fieldfalseTimer locks only when true.
requiredVotesNumber input10Integer, 1–10,000.
autoCloseBoolean fieldfalseClose at the selected total.
autoCloseVotesNumber input10Integer, 1–10,000.

actions.publicVotingStart​

Blockly: pl_action_public_voting_start · Kind: statement.

No options. Enables voting, generating a secure share token only when absent or obsolete. Votes and cooldowns are retained. Permanent disabling rejects the action.

actions.publicVotingStop​

Blockly: pl_action_public_voting_stop · Kind: statement.

No options. Disables voting without resetting history, accumulated effects, the share token, or cooldowns. It does not end the lock.

actions.publicVotingTimes​

Blockly: pl_action_public_voting_times · Kind: statement.

Sets time effects for future votes; does not apply time immediately.

OptionKindDefaultConstraint
addSecondsNumber input3600Integer seconds, 60–86,400.
removeSecondsNumber input3600Positive integer seconds, 60–86,400.
randomMinSecondsNumber input-7200Integer seconds, −86,400–0.
randomMaxSecondsNumber input14400Integer seconds, 0–172,800.

actions.publicVotingVisibility​

Blockly: pl_action_public_voting_visibility · Kind: statement.

Changes listing and account requirements without enabling voting.

OptionKindDefaultConstraint
showPublicBoolean fieldfalseList on the public voting page while eligible.
requireAccountBoolean fieldfalseRequire voters to sign in.

actions.quizGenerateAssign​

Blockly: pl_action_quiz_generate_assign · Kind: statement.

Generates 1–20 questions with 2–6 choices. Subscribed wearers get 10 included generation attempts per rolling 24 hours, shared across their programmable locks. Failed generation attempts also count. After that, or without a subscription, generation uses the wearer's own OpenRouter key, model, and credits configured in Settings → General, Bot Coach, or AI Coach. Generation runs in the background; a private quiz task is assigned only after validation. Requires the task module. The deadline begins at assignment. One scored attempt. Durations are limited to 7 days.

OptionKindField default / input typeAllowed values or usage
expiryUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
failureTypeField: string"none""none"; "add_time" — add time; "freeze"; "pillory".
failureUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
expiryTypeField: string"none""none"; "add_time" — add time; "freeze"; "pillory".
expiryPenaltyUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
rewardUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
questionCountInputNumberSupported value expression.
answerCountInputNumberSupported value expression.
instructionsInputStringSupported value expression.
expiryInputNumberSupported value expression.
passPercentageInputNumberSupported value expression.
failureDurationInputNumberSupported value expression.
expiryPenaltyDurationInputNumberSupported value expression.
rewardDurationInputNumberSupported value expression.

actions.removeTime​

Blockly: pl_action_remove_time · Kind: statement.

Removes the supplied amount using the selected unit. Reducing the countdown does not remove other unlock requirements.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

actions.resetGameCooldown​

Blockly: pl_action_reset_game_cooldown · Kind: statement.

Clears the current cooldown for the selected activity without changing its configured duration, completion history or daily progress. PLE clears all control cooldowns in this extension, including the control running this reset. Jigsaw and Daily Puzzles allow one new round when waiting on cooldown; an unfinished puzzle stays active. All activities includes PLE, tasks and both puzzle extensions.

OptionKindField default / input typeAllowed values or usage
gameField: string"WHEEL""WHEEL" — wheel; "DICE" — dice; "CHESS" — chess; "CARD" — card game; "TASK" — tasks; "JIGSAW" — Jigsaw; "DAILY_PUZZLES" — Daily Puzzles; "PLE"; "ALL" — all activities.

actions.scheduleCustomEventAfter​

Blockly: pl_action_schedule_custom_event_after · Kind: statement.

Schedules this event once after the specified duration. Delays below five seconds, including zero and negative values, are automatically increased to five seconds. If it is already scheduled, the previous schedule is replaced. Repeated scheduling does not create multiple occurrences. Long-running chains remain subject to server safety limits.

OptionKindField default / input typeAllowed values or usage
eventIdField: string"1""1" — Custom event 1; "2" — Custom event 2; "3" — Custom event 3; "4" — Custom event 4; "5" — Custom event 5.
durationInputNumberSupported value expression.

actions.scheduleCustomEventAt​

Blockly: pl_action_schedule_custom_event_at · Kind: statement.

Schedules this event once at least five seconds in the future. If the selected date/time has already passed, the scheduling request is skipped and the following blocks continue. A skipped request leaves any existing schedule unchanged. Otherwise, the previous schedule is replaced. Repeated scheduling does not create multiple occurrences. Long-running chains remain subject to server safety limits.

OptionKindField default / input typeAllowed values or usage
eventIdField: string"1""1" — Custom event 1; "2" — Custom event 2; "3" — Custom event 3; "4" — Custom event 4; "5" — Custom event 5.
timestampInputNumberSupported value expression.

actions.secondaryVibrationStart​

Blockly: pl_action_secondary_vibration_start · Kind: statement.

Vibrates the first connected secondary accessory, such as a Lovense toy or Pear Flower II. Duration is limited to 5 minutes and intensity to 1-100%.

OptionKindField default / input typeAllowed values or usage
durationTextField: string"30"Literal text.
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours.
intensityTextField: string"50"Literal text.
patternField: string"steady""steady"; "pulse"; "wave"; "climb"; "tease"; "alternate".

actions.secondaryVibrationStartValues​

Blockly: pl_action_secondary_vibration_start_values · Kind: statement.

Vibrates the first connected secondary accessory using Number inputs. Duration is limited to 5 minutes and intensity to 1-100%.

OptionKindField default / input typeAllowed values or usage
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours.
patternField: string"steady""steady"; "pulse"; "wave"; "climb"; "tease"; "alternate".
durationInputNumberSupported value expression.
intensityInputNumberSupported value expression.

actions.secondaryVibrationStop​

Blockly: pl_action_secondary_vibration_stop · Kind: statement.

Stops vibration on all enabled secondary accessories.

No options; call actions.secondaryVibrationStop().

actions.setEndTime​

Blockly: pl_action_set_end_time · Kind: statement.

Sets the timer's end to a date/time or Unix timestamp in seconds, up to 365 days from now. The lock's maximum end time still applies. Timer locks only; unfreeze the timer first. A past date expires the timer but does not bypass other unlock requirements.

OptionKindField default / input typeAllowed values or usage
timestampInputNumberSupported value expression.

actions.setInfoHidden​

Blockly: pl_action_set_info_hidden · Kind: statement.

Card Game only. Shows or hides the remaining-card count and the identities of undrawn cards. This does not affect general lock information.

OptionKindField default / input typeAllowed values or usage
modeField: string"VISIBLE""VISIBLE" — visible; "HIDDEN" — hidden.

actions.setLockHistoryVisibility​

Blockly: pl_action_set_lock_history_visibility · Kind: statement.

Shows or hides the lock event history.

OptionKindField default / input typeAllowed values or usage
modeField: string"SHOW""SHOW" — show; "HIDE" — hide.

actions.setProgrammableHistoryVisibility​

Blockly: pl_action_set_programmable_history_visibility · Kind: statement.

Shows or hides program triggers, actions, and execution results from the wearer.

OptionKindField default / input typeAllowed values or usage
modeField: string"SHOW""SHOW" — show; "HIDE" — hide.

actions.setRemainingTime​

Blockly: pl_action_set_remaining_time · Kind: statement.

Replaces the remaining countdown with this duration, measured from now. Use days, hours, minutes and seconds, or connect a duration, calculation or variable in seconds. Timer locks only; unfreeze first. The maximum end time and 365-day limit still apply. Zero expires the timer without bypassing other unlock requirements.

OptionKindField default / input typeAllowed values or usage
durationInputNumberSupported value expression.

actions.setRequiredGreens​

Blockly: pl_action_set_required_greens · Kind: statement.

Sets the current game's green-card target to a whole number from 1 to 5. Accepts a number, variable, or calculation. Keeps collected greens and adds any missing greens to the deck. If the target is already met, opens the normal unlock decision. Unavailable during a pending green-card decision.

OptionKindField default / input typeAllowed values or usage
amountInputNumberSupported value expression.

actions.setTimeLockedVisibility​

Blockly: pl_action_set_time_locked_visibility · Kind: statement.

Shows or hides elapsed lock time.

OptionKindField default / input typeAllowed values or usage
modeField: string"SHOW""SHOW" — show; "HIDE" — hide.

actions.setTimerVisibility​

Blockly: pl_action_set_timer_visibility · Kind: statement.

Shows or hides remaining time.

OptionKindField default / input typeAllowed values or usage
modeField: string"SHOW""SHOW" — show; "HIDE" — hide.

actions.shockStart​

Blockly: pl_action_shock_start · Kind: statement.

Starts shock using fixed values. Duration and intensity are typed directly into the block. Use the variable/value version if you want to plug in Number or variable blocks.

OptionKindField default / input typeAllowed values or usage
durationTextField: string"30"Literal text.
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours.
intensityTextField: string"50"Literal text.
modeField: string"DEFAULT""DEFAULT" — default; "RANDOM" — random; "BERSERK" — berserk.

actions.shockStartValues​

Blockly: pl_action_shock_start_values · Kind: statement.

Starts shock using Number inputs. Attach Number, math, or variable blocks for duration and intensity, then choose whether duration is in seconds, minutes, or hours.

OptionKindField default / input typeAllowed values or usage
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours.
modeField: string"DEFAULT""DEFAULT" — default; "RANDOM" — random; "BERSERK" — berserk.
durationInputNumberSupported value expression.
intensityInputNumberSupported value expression.

actions.shockStop​

Blockly: pl_action_shock_stop · Kind: statement.

Requests that the shock stop.

No options; call actions.shockStop().

actions.taskAssign​

Blockly: pl_action_task_assign · Kind: statement.

Assigns a task through the Tasks module. Photo verification applies when verification is required. Choose one photo or before and after photos. For before and after, upload the first photo before starting the timer and the second when submitting verification. Description and minimum duration are optional. The deadline can be relative to assignment, the end of the wearer's current day, or a specific date and time in the wearer's time zone.

OptionKindField default / input typeAllowed values or usage
actorField: string"extension""extension"; "keyholder"; "wearer".
photoVerificationModeField: string"single""single" — one photo; "before_after" — before and after photos.
deadlineModeField: string"DURATION""DURATION" — relative duration; "END_OF_TODAY" — end of today; "SPECIFIC" — specific date and time.
maxDurationUnitField: string"MINUTE""MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
deadlineDateField: stringCurrent editor dateLiteral date text, YYYY-MM-DD.
deadlineTimeField: string"23:59"Literal text.
taskTextInputStringSupported value expression.
descriptionInputStringSupported value expression.
pointsInputNumberSupported value expression.
verificationRequiredInputBooleanSupported value expression.
durationInputNumberSupported value expression.
maxDurationAmountInputNumberSupported value expression.

actions.taskAssignExisting​

Blockly: pl_action_task_assign_existing · Kind: statement.

Assigns a configured task from this lock's task module, preserving its settings and writing-task link. Following blocks run immediately. Use “when task assigned” to store the event task run ID, then match it in “when task completed” before performing a success-only action. Missing tasks must be selected again on the destination lock.

OptionKindField default / input typeAllowed values or usage
taskIdField: string"__no_task__""__no_task__" — No configured tasks available.

actions.taskComplete​

Blockly: pl_action_task_complete · Kind: statement.

Marks the current task as completed or failed.

OptionKindField default / input typeAllowed values or usage
successfulInputBooleanSupported value expression.

actions.taskStartTimer​

Blockly: pl_action_task_start_timer · Kind: statement.

Starts the timer for the currently active timed task.

No options; call actions.taskStartTimer().

actions.toggleFreeze​

Blockly: pl_action_toggle_freeze · Kind: statement.

Unfreezes a frozen lock, or freezes it for 1 hour. In trap mode, freezing remains indefinite.

No options; call actions.toggleFreeze().

actions.unfreeze​

Blockly: pl_action_unfreeze · Kind: statement.

Requests that the lock leave its frozen state.

No options; call actions.unfreeze().

actions.vibrationStart​

Blockly: pl_action_vibration_start · Kind: statement.

Starts vibration using fixed values. Duration is limited to 5 minutes; intensity and frequency are limited to 1-100%.

OptionKindField default / input typeAllowed values or usage
durationTextField: string"30"Literal text.
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours.
intensityTextField: string"50"Literal text.
frequencyTextField: string"50"Literal text.

actions.vibrationStartValues​

Blockly: pl_action_vibration_start_values · Kind: statement.

Starts vibration using Number inputs. Duration is limited to 5 minutes; intensity and frequency are limited to 1-100%.

OptionKindField default / input typeAllowed values or usage
durationUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours.
durationInputNumberSupported value expression.
intensityInputNumberSupported value expression.
frequencyInputNumberSupported value expression.

actions.vibrationStop​

Blockly: pl_action_vibration_stop · Kind: statement.

Requests that the selected device's vibration stop.

No options; call actions.vibrationStop().

ui APIs​

The UI APIs are the eight calls listed below. There are no dedicated heading, separator, panel, or layout blocks. Use ui.text for heading-like text or a line of separator characters. Top-level button(...) calls create separate controls; the handler runs actions when clicked and does not define child controls inside a panel.

UI text and modal messages are plain strings, with no Markdown or HTML rendering. In source, "First\nSecond" decodes to a string containing a real newline; "First\\nSecond" contains a literal backslash and n. The PLE program output area preserves newlines from ui.text. The current ui.modal message renderer uses normal HTML whitespace, so newline characters collapse into spaces rather than guaranteed visible line breaks. Use ui.text when explicit line breaks must be preserved.

ui.actionSheet​

Blockly: pl_action_ui_action_sheet · Kind: statement.

Shows an action sheet with 1-12 options. Each option can run its own branch when selected.

With optionCount: N, supply option1Label … optionNLabel and onOption1 … onOptionN. Callbacks are named options; do not add sockets beyond the selected count.

OptionKindField default / input typeAllowed values or usage
optionCountField: number5Number; min 1, max 12, precision 1.
titleInputStringSupported value expression.
messageInputStringSupported value expression.
buttonTextInputStringSupported value expression.
option1LabelInputStringSupported value expression.
onOption1Branch() => { ... }Named branch in the options object.
option2LabelInputStringSupported value expression.
onOption2Branch() => { ... }Named branch in the options object.
option3LabelInputStringSupported value expression.
onOption3Branch() => { ... }Named branch in the options object.
option4LabelInputStringSupported value expression.
onOption4Branch() => { ... }Named branch in the options object.
option5LabelInputStringSupported value expression.
onOption5Branch() => { ... }Named branch in the options object.

ui.confirm​

Blockly: pl_action_ui_confirm · Kind: statement.

Shows a confirmation dialog in runtime UI. Runs the confirm branch when user confirms. Runs the cancel branch when user cancels.

For a reusable Boolean answer, wrap this action in a returning function and call functions.return in each callback; see Returning a confirmation answer. ui.confirm itself is a statement and cannot be used as a value.

onConfirm and onCancel are independently optional. Omit a callback, or use an empty callback, when that outcome needs no branch actions. The dialog still appears; an omitted selected branch does nothing.

OptionKindField default / input typeAllowed values or usage
titleInputStringSupported value expression.
messageInputStringSupported value expression.
confirmTextInputStringSupported value expression.
cancelTextInputStringSupported value expression.
onConfirmBranch() => { ... }Named branch in the options object.
onCancelBranch() => { ... }Named branch in the options object.

ui.input​

Blockly: pl_action_ui_input · Kind: statement.

Shows a text input prompt in runtime UI and stores the submitted text in the selected variable. Cancel leaves the variable unchanged.

Declare a string variable and supply its identifier in var. Cancel leaves its value unchanged.

OptionKindField default / input typeAllowed values or usage
varVariable selectorDeclared identifierChoose a declared variable; no quoted name or generated ID.
titleInputStringSupported value expression.
messageInputStringSupported value expression.
placeholderInputStringSupported value expression.
defaultValueInputStringSupported value expression.
confirmTextInputStringSupported value expression.
cancelTextInputStringSupported value expression.

ui.modal​

Blockly: pl_action_ui_modal · Kind: statement.

Shows a modal dialog in runtime UI. Button click runs confirm branch. Closing without button click runs cancel branch.

onConfirm and onCancel are independently optional. Omit either or both when no close/dismiss actions are needed; the modal still appears. The playground includes both callbacks only to demonstrate the two outcomes.

message is plain text, with no Markdown or HTML rendering. Source \n escapes become real newlines, but the current modal renderer collapses them as normal HTML whitespace. They do not display as literal \n unless the source contains an escaped backslash. Use ui.text for output that preserves explicit line breaks.

OptionKindField default / input typeAllowed values or usage
titleInputStringSupported value expression.
messageInputStringSupported value expression.
buttonTextInputStringSupported value expression.
onConfirmBranch() => { ... }Named branch in the options object.
onCancelBranch() => { ... }Named branch in the options object.

ui.popover​

Blockly: pl_action_ui_popover · Kind: statement.

Shows a popover menu with 1-12 options. Each option can run its own branch when selected.

With optionCount: N, supply option1Label … optionNLabel and onOption1 … onOptionN. Callbacks are named options; do not add sockets beyond the selected count.

OptionKindField default / input typeAllowed values or usage
optionCountField: number5Number; min 1, max 12, precision 1.
option1LabelInputStringSupported value expression.
onOption1Branch() => { ... }Named branch in the options object.
option2LabelInputStringSupported value expression.
onOption2Branch() => { ... }Named branch in the options object.
option3LabelInputStringSupported value expression.
onOption3Branch() => { ... }Named branch in the options object.
option4LabelInputStringSupported value expression.
onOption4Branch() => { ... }Named branch in the options object.
option5LabelInputStringSupported value expression.
onOption5Branch() => { ... }Named branch in the options object.

ui.spinWheel​

Blockly: pl_action_ui_spin_wheel · Kind: statement.

Shows a spin wheel in runtime UI. Fill 2-12 options. Spinning picks one option at random and runs that option's branch.

With optionCount: N, supply option1Label … optionNLabel and onOption1 … onOptionN. Each option also has optionNColor. Callbacks are named options; do not add sockets beyond the selected count.

OptionKindField default / input typeAllowed values or usage
optionCountField: number6Number; min 2, max 12, precision 1.
titleInputStringSupported value expression.
messageInputStringSupported value expression.
option1LabelInputStringSupported value expression.
option1ColorInputString / ColourSupported value expression.
onOption1Branch() => { ... }Named branch in the options object.
option2LabelInputStringSupported value expression.
option2ColorInputString / ColourSupported value expression.
onOption2Branch() => { ... }Named branch in the options object.
option3LabelInputStringSupported value expression.
option3ColorInputString / ColourSupported value expression.
onOption3Branch() => { ... }Named branch in the options object.
option4LabelInputStringSupported value expression.
option4ColorInputString / ColourSupported value expression.
onOption4Branch() => { ... }Named branch in the options object.
option5LabelInputStringSupported value expression.
option5ColorInputString / ColourSupported value expression.
onOption5Branch() => { ... }Named branch in the options object.
option6LabelInputStringSupported value expression.
option6ColorInputString / ColourSupported value expression.
onOption6Branch() => { ... }Named branch in the options object.

ui.text​

Blockly: pl_action_ui_text · Kind: statement.

Prints plain text into the UI preview area. Newline characters are preserved. There are no heading-level, style, separator, or layout options, and Markdown/HTML is displayed as text. A heading-like line or dash separator is ordinary text output.

OptionKindField default / input typeAllowed values or usage
textInputStringSupported value expression.

ui.toast​

Blockly: pl_action_ui_toast · Kind: statement.

Select info, success, warning, or error and supply title/message for temporary feedback.

OptionKindField default / input typeAllowed values or usage
toastTypeField: string"info""info"; "success"; "warning"; "error".
titleInputStringSupported value expression.
messageInputStringSupported value expression.

calendar APIs​

calendar.createEntry​

Blockly: pl_calendar_create_entry · Kind: statement.

Creates an entry in the wearer's calendar. When whole day is true, only the wearer-local date is used and duration is ignored; all-day reminders are sent at 08:00 local time. Timed reminders are sent 10 minutes before the event starts. Attribution defaults to the wearer; keyholder attribution requires a shared lock. Write protection is only applied to keyholder-attributed shared events. Shared locks require calendar-management permission. Connect a Color block to choose an entry color, or leave it empty to use the calendar category color.

OptionKindField default / input typeAllowed values or usage
creatorRoleField: string"wearer""wearer"; "keyholder".
titleInputStringSupported value expression.
startsAtInputNumberSupported value expression.
allDayInputBooleanSupported value expression.
durationInputNumberSupported value expression.
notesInputStringSupported value expression.
reminderEnabledInputBooleanSupported value expression.
wearerWriteProtectedInputBooleanSupported value expression.
colorInputStringSupported value expression.

calendar.createTask​

Blockly: pl_calendar_create_task · Kind: statement.

Schedules a task from the calendar. When photo verification is required, choose a single photo or before and after photos. Before and after verification requires one photo before the task and another after completion. When whole day is true, only the wearer-local date is used, the completion window is ignored, and the task is assigned at 08:00 local time. Timed reminders are sent 10 minutes before the task starts; all-day reminders are sent at 08:00. Attribution defaults to the wearer; keyholder attribution requires a shared lock. Write protection is only applied to keyholder-attributed shared tasks. Shared locks require the Task Module; self-lock tasks remain isolated from Task Module points. Connect a Color block to choose an entry color, or leave it empty to use the calendar category color.

OptionKindField default / input typeAllowed values or usage
creatorRoleField: string"wearer""wearer"; "keyholder".
photoVerificationModeField: string"single""single" — single photo; "before_after" — before and after photos.
titleInputStringSupported value expression.
startsAtInputNumberSupported value expression.
allDayInputBooleanSupported value expression.
durationInputNumberSupported value expression.
notesInputStringSupported value expression.
reminderEnabledInputBooleanSupported value expression.
wearerWriteProtectedInputBooleanSupported value expression.
requiresPhotoInputBooleanSupported value expression.
colorInputStringSupported value expression.

functions APIs​

functions.call​

Blockly: pl_function_call · Kind: statement, or value when the definition has returnsValue: true.

Pass one value to each named parameter.

Define the target with functions.def. Its signature determines the actual number and types of arg1 … arg4; the unspecialized shape below has four Any sockets. Supply every declared parameter in order.

OptionKindField default / input typeAllowed values or usage
nameField: string"function_1"Literal text.
arg1InputAnySupported value expression.
arg2InputAnySupported value expression.
arg3InputAnySupported value expression.
arg4InputAnySupported value expression.

functions.def​

Blockly: pl_function_def · Kind: root.

Use the gear to edit parameters and enable a return value. Select this function and open Functions to get its parameter and return blocks.

For new code add parameters: { parameterName: "type", ... } (0–4 parameters); this is adapter syntax in addition to the fields below. Do not combine it with $state. The params field is legacy configuration; preserve existing legacy exports.

returnsValue: true enables the return value input and makes calls usable in expressions. Omit it for a statement-only function.

OptionKindField default / input typeAllowed values or usage
nameField: string"function_1"Literal text.
paramsField: string""Literal text.
returnsValueSignaturefalseLiteral Boolean.
returnInputAnyFinal return value; available when returnsValue is true.
doBranch() => { ... }May instead be the trailing callback.

functions.return​

Blockly: pl_function_return · Kind: statement.

Return a value immediately from the surrounding returning function, skipping its remaining steps, including enclosing loops. Use only inside a definition with returnsValue: true.

OptionKindField default / input typeAllowed values or usage
valueInputAnySupported value expression; an empty input returns null.

functions.parameter​

Blockly: pl_function_parameter · Kind: value · Output: the selected parameter’s declared type (Any before selection).

This parameter belongs to the surrounding function and exists only during that call.

Within a function prefer { name: "parameterName" }. The adapter resolves name to parameterId. The raw ID field and $state are for retaining exported/legacy metadata; do not invent IDs. A boolean parameter is valid directly in native conditions and Boolean operations; numeric parameters retain their numeric value in assignments and arithmetic. See parameter types in conditions and arithmetic.

OptionKindField default / input typeAllowed values or usage
parameterIdField: string"""" — Choose parameter.

functions.parameterSet​

Blockly: pl_function_parameter_set · Kind: statement.

This parameter belongs to the surrounding function and exists only during that call.

Within a function prefer { name: "parameterName", value: expression }. The parameter type determines the value socket. Preserve raw ID/state only when maintaining an existing exported block.

OptionKindField default / input typeAllowed values or usage
parameterIdField: string"""" — Choose parameter.
valueInputAnySupported value expression.

loops APIs​

loops.forEach​

Blockly: pl_loop_for_each · Kind: statement.

Runs the connected blocks once for each number in the list, in order. The selected variable receives the current number. The list is captured when the loop starts and can contain up to 100 numbers.

The var option is a declared workspace variable identifier. Use a number variable for the current list item. Iteration preserves list order and captures the list when the loop starts.

OptionKindField default / input typeAllowed values or usage
varVariable selectorDeclared identifierChoose a declared variable; no quoted name or generated ID.
listInputArraySupported value expression.
doBranch() => { ... }May instead be the trailing callback.

loops.repeat​

Blockly: pl_loop_repeat · Kind: statement.

Supply a number. Uses a whole-number count from 0–100; larger values are capped at 100.

OptionKindField default / input typeAllowed values or usage
timesInputNumberSupported value expression.
doBranch() => { ... }May instead be the trailing callback.

loops.whileUntil​

Blockly: pl_loop_while_until · Kind: statement.

Checks the condition before each iteration. While runs when true; until runs when false. At most 100 iterations.

OptionKindField default / input typeAllowed values or usage
modeField: string"WHILE""WHILE" — while; "UNTIL" — until.
condInputBooleanSupported value expression.
doBranch() => { ... }May instead be the trailing callback.

conditions APIs​

conditions.compare​

Blockly: pl_condition_compare · Kind: value · Output: Boolean.

Compares two values using the selected operator. The result is true when the comparison matches.

OptionKindField default / input typeAllowed values or usage
opField: string"EQ""EQ" — =; "NEQ" — !=; "LT" — <; "LTE" — <=; "GT" — >; "GTE" — >=.
aInputAnySupported value expression.
bInputAnySupported value expression.

conditions.eventText​

Blockly: pl_condition_event_text · Kind: value · Output: Boolean.

Compares a text value from the enclosing event. Equals and does not equal are case-sensitive; contains, does not contain, starts with, and ends with ignore case.

This block uses literal fields, including value. For comparisons against calculated text use conditions.compare({ op: "EQ", a: expression, b: expression }) instead.

OptionKindField default / input typeAllowed values or usage
keyField: string"EVENT_NAME""EVENT_NAME" — event name; "TRIGGERING_EXTENSION" — triggering extension; "VARIABLE_NAME" — variable name; "VARIABLE_PREVIOUS_VALUE" — previous value; "VARIABLE_VALUE" — new value; "VARIABLE_CHANGE_SOURCE" — change source; "BEFORE_RELEASE_AT" — before release date; "AFTER_RELEASE_AT" — after release date; "MAX_DATE_BEFORE" — before max release date; "MAX_DATE_STRING" — new maximum date; "SHARE_LINK_VOTER_USERNAME" — voter username; "PILLORY_REASON" — pillory reason; "VOTE_TYPE" — vote type (add / remove / random); "TASK_RUN_ID" — task run id; "TASK_ID" — task id; "TASK_STATUS" — task status; "TASK_TITLE" — task title; "TASK_DESCRIPTION" — task description; "TASK_APPROVAL_TYPE" — approval type (keyholder / community / auto / bot); "TASK_REJECTION_REASON" — rejection reason; "CALENDAR_EVENT_ID" — calendar event id; "CALENDAR_OCCURRENCE_DATE" — calendar occurrence date; "QUIZ_RUN_ID" — quiz run id; "VERIFICATION_CODE" — verification code; "GEOFENCE_ZONE_ID" — area id; "GEOFENCE_ZONE_NAME" — area name; "GEOFENCE_ZONE_TYPE" — area type; "COACH_TYPE" — Coach type; "COACH_NAME" — Coach name; "SELF_LOCK_MODE" — self-lock mode; "PARENTAL_APP_NAME" — app name; "PARENTAL_APP_PACKAGE_NAME" — package name; "PARENTAL_BLOCK_REASON" — block reason; "PARENTAL_RULE_SOURCE" — rule source; "PARENTAL_DEVICE_ID" — parental-control device id; "PARENTAL_WEBSITE_DOMAIN" — website domain; "WHEEL_OPTION_NAME" — wheel option name; "WHEEL_RESULT_TYPE" — wheel result type; "WHEEL_RESULT_MESSAGE" — wheel result message; "WHEEL_OPTION_ID" — wheel option id; "WHEEL_ACTION_TEXT" — action text; "DICE_TYPES" — dice types; "DICE_PLAYER_VALUES" — player dice values (comma-separated); "DICE_BOT_VALUES" — bot dice values (comma-separated); "ARCHIVE_SOURCE" — archive source; "ARCHIVE_ACTOR_ROLE" — archive actor role; "ARCHIVE_LOCK_ID" — archive lock id; "CALENDAR_EVENT_TITLE" — calendar event title; "CALENDAR_EVENT_TYPE" — calendar event type; "CALENDAR_EVENT_NOTES" — calendar event notes; "CALENDAR_START_AT" — start date and time; "CALENDAR_END_AT" — end date and time; "CALENDAR_CHANGED_FIELDS" — changed fields.
opField: string"EQ""EQ" — equals; "NEQ" — does not equal; "CONTAINS" — contains; "NOT_CONTAINS" — does not contain; "STARTS_WITH" — starts with; "ENDS_WITH" — ends with.
valueField: string"Task A"Literal text.

conditions.isOneOf​

Blockly: pl_condition_is_one_of · Kind: value · Output: Boolean.

Checks whether the value equals any listed option. Change the option count to add or remove comparisons.

With optionCount: N, use option0 … optionNminus1 (zero-based). Supply 2–12 options.

OptionKindField default / input typeAllowed values or usage
optionCountField: number2Number; min 2, max 12, precision 1.
valueInputAnySupported value expression.
option0InputAnySupported value expression.
option1InputAnySupported value expression.

conditions.lockFrozen​

Blockly: pl_condition_lock_frozen · Kind: value · Output: Boolean.

Checks whether the lock is currently frozen.

No options; call conditions.lockFrozen().

conditions.lockIs​

Blockly: pl_condition_lock_is · Kind: value · Output: Boolean.

Checks runtime lock state flags from session data.

OptionKindField default / input typeAllowed values or usage
flagField: string"FROZEN""FROZEN" — frozen; "UNLOCKABLE" — unlockable; "TRUSTED" — trusted; "TIME_LOGS_HIDDEN" — remaining time hidden; "LOCK_HISTORY_HIDDEN" — lock event history hidden; "SELF_LOCK" — self lock; "SHARED_LOCK" — shared lock; "HYGIENIC_UNLOCK_ACTIVE" — hygienic unlock active; "TASK_ASSIGNED" — task assigned; "IN_PILLORY" — in pillory; "GAG_ENABLED" — community-chat gag enabled; "PARENTAL_CONTROL_ENABLED" — parental control enabled; "PARENTAL_CONTROL_SELF_LOCK_ENABLED" — parental control self-lock enabled; "HARDCORE_MODE_ENABLED" — Hardcore Mode enabled; "PORN_FILTER_ENABLED" — porn filter enabled; "BROWSER_CONTROL_ENABLED" — browser control enabled; "CONTENT_SHIELD_MODE_A_ENABLED" — Content Shield Mode A enabled; "CONTENT_SHIELD_MODE_B_ENABLED" — Content Shield Mode B enabled; "CONTENT_SHIELD_MODE_C_ENABLED" — Content Shield Mode C enabled; "SECURE_FOLDER_BLOCKING_ENABLED" — Secure Folder blocking enabled; "SETTINGS_BLOCKING_ENABLED" — Settings blocking enabled; "PARENTAL_CONTROL_ACTION_SHOCK_ENABLED" — parental-control action shock enabled; "CONTENT_SHIELD_SUBSCRIPTION_ACTIVE" — Content Shield subscription active; "AI_COACH_ENABLED" — AI Coach enabled; "BOT_COACH_ENABLED" — Bot Coach enabled.

conditions.logic​

Blockly: pl_condition_logic · Kind: value · Output: Boolean.

Combines two conditions. XOR is true when exactly one condition is true; NAND, NOR, and XNOR invert AND, OR, and XOR.

OptionKindField default / input typeAllowed values or usage
opField: string"AND""AND" — and; "OR" — or; "XOR" — xor; "NAND" — nand; "NOR" — nor; "XNOR" — xnor.
aInputBooleanSupported value expression.
bInputBooleanSupported value expression.

conditions.not​

Blockly: pl_condition_not · Kind: value · Output: Boolean.

Inverts a boolean condition. true becomes false, and false becomes true.

OptionKindField default / input typeAllowed values or usage
boolInputBooleanSupported value expression.

conditions.randomChance​

Blockly: pl_condition_random_chance · Kind: value · Output: Boolean.

Returns a random Boolean for the percentage. Store the result in a variable if several later checks must share one decision. 0 always selects false and 100 always selects true. A 10% chance does not guarantee one success in every ten runs; use the randomness testing workflow to exercise both branches.

OptionKindField default / input typeAllowed values or usage
chanceInputNumberSupported value expression.

conditions.textContains​

Blockly: pl_condition_text_contains · Kind: value · Output: Boolean.

Checks whether one text includes another text. Comparison is case-insensitive. Returns true when the target text is found; otherwise false.

OptionKindField default / input typeAllowed values or usage
textInputStringSupported value expression.
subtextInputStringSupported value expression.

logic APIs​

logic.boolean​

Blockly: logic_boolean · Kind: value · Output: Boolean.

Returns either true or false.

OptionKindField default / input typeAllowed values or usage
boolField: string"TRUE""TRUE" — true; "FALSE" — false.

logic.compare​

Blockly: logic_compare · Kind: value · Output: Boolean.

Return true if both inputs equal each other.

OptionKindField default / input typeAllowed values or usage
opField: string"EQ""EQ" — =; "NEQ" — ≠; "LT" — <; "LTE" — ‏≤; "GT" — ‏>; "GTE" — ‏≥.
aInputAnySupported value expression.
bInputAnySupported value expression.

logic.negate​

Blockly: logic_negate · Kind: value · Output: Boolean.

Returns true if the input is false. Returns false if the input is true.

OptionKindField default / input typeAllowed values or usage
boolInputBooleanSupported value expression.

logic.operation​

Blockly: logic_operation · Kind: value · Output: Boolean.

Return true if both inputs are true.

OptionKindField default / input typeAllowed values or usage
opField: string"AND""AND" — and; "OR" — or.
aInputBooleanSupported value expression.
bInputBooleanSupported value expression.

logic.ternary​

Blockly: logic_ternary · Kind: value · Output: Any.

Returns one of two values depending on a condition. Connect this block to a color, text, number, or true/false input. For example, choose green for a completed task and red otherwise. Both values must fit the receiving input. Only the chosen value is evaluated.

OptionKindField default / input typeAllowed values or usage
ifInputBooleanSupported value expression.
thenInputAnySupported value expression.
elseInputAnySupported value expression.

math APIs​

math.arithmetic​

Blockly: math_arithmetic · Kind: value · Output: Number.

Performs basic arithmetic between two numbers. Supports add, subtract, multiply, divide, power, and modulo (%). Returns a numeric result. Modulo supports live numeric inputs, keeps the sign of the dividend (-7 % 3 is -1), and returns 0 for a zero divisor, matching PLE division. For example, score % 2 == 0 checks for an even score.

OptionKindField default / input typeAllowed values or usage
opField: string"ADD""ADD" — +; "MINUS" — -; "MULTIPLY" — ×; "DIVIDE" — ÷; "POWER" — ^; "MODULO" — %.
aInputNumberSupported value expression.
bInputNumberSupported value expression.

math.change​

Blockly: math_change · Kind: statement.

Adds a numeric amount to a workspace variable. Existing change variable by blocks use this spelling when switching to Code and keep their block and variable identities when switching back. Use it inside an event or function body; it has no return value, callback branches, or dialog pause.

OptionKindField default / input typeAllowed values or usage
varVariable selectorDeclared identifierChoose a numeric variable (number, integer, or date_time), or a legacy any variable holding a number; no quoted name or generated ID.
deltaInputNumberAmount to add; negative amounts subtract. An unconnected input defaults to 1. Literal numeric inputs are truncated toward zero by the existing compiler.
let completedTasks: integer = 0;

on.taskCompleted(() => {
math.change({ var: completedTasks, delta: 1 });
print(completedTasks);
});

math.constant​

Blockly: math_constant · Kind: value · Output: Number.

Provides common mathematical constants. Use values like pi, e, golden ratio, square roots, and infinity.

OptionKindField default / input typeAllowed values or usage
constantField: string"PI""PI" — π; "E" — e; "GOLDEN_RATIO" — φ; "SQRT2" — sqrt(2); "SQRT1_2" — sqrt(½); "INFINITY" — ∞.

math.minmax​

Blockly: pl_math_minmax · Kind: value · Output: Number.

Returns the smaller or larger value of two numbers.

OptionKindField default / input typeAllowed values or usage
opField: string"MIN""MIN" — min; "MAX" — max.
aInputNumberSupported value expression.
bInputNumberSupported value expression.

math.modulo​

Blockly: math_modulo · Kind: value · Output: Number.

Computes the remainder after division for fixed numeric inputs and a nonzero divisor. For live variables or event values, use the arithmetic % operator instead: score % 2 (or math.arithmetic({ op: "MODULO", a: score, b: 2 })).

OptionKindField default / input typeAllowed values or usage
dividendInputNumberSupported value expression.
divisorInputNumberSupported value expression.

math.number​

Blockly: math_number · Kind: value · Output: Number.

A number.

OptionKindField default / input typeAllowed values or usage
numField: number0Literal number.

math.randomFloat​

Blockly: math_random_float · Kind: value · Output: Number.

Generates a random decimal from 0 (inclusive) to 1 (exclusive).

No options; call math.randomFloat().

math.randomInt​

Blockly: math_random_int · Kind: value · Output: Number.

Generates a random integer in an inclusive range. Result is between min and max, including both ends.

OptionKindField default / input typeAllowed values or usage
fromInputNumberSupported value expression.
toInputNumberSupported value expression.

math.round​

Blockly: math_round · Kind: value · Output: Number.

Rounds a number using the selected mode. Round to nearest, always up, or always down. Returns an integer.

OptionKindField default / input typeAllowed values or usage
opField: string"ROUND""ROUND" — round; "ROUNDUP" — round up; "ROUNDDOWN" — round down.
numInputNumberSupported value expression.

math.single​

Blockly: math_single · Kind: value · Output: Number.

Applies a single-number math function. Includes sqrt, absolute value, negation, logs, exponentials, and trig operations. Returns a number.

OptionKindField default / input typeAllowed values or usage
opField: string"ROOT""ROOT" — square root; "ABS" — absolute; "NEG" — -; "LN" — ln; "LOG10" — log10; "EXP" — e^; "POW10" — 10^.
numInputNumberSupported value expression.

values APIs​

values.color​

Blockly: pl_value_color · Kind: value · Output: String / Colour.

Pick a color value.

OptionKindField default / input typeAllowed values or usage
colourField: string"#4f46e5"Literal text.

values.currentTime​

Blockly: pl_value_current_time · Kind: value · Output: Number.

Reads current time when the script runs. The Unix timestamp is returned in whole seconds. Hours use 24-hour format (0-23), and weekdays use 1=Monday through 7=Sunday. Local time uses the wearer's configured timezone; UTC is used if no valid timezone is available.

OptionKindField default / input typeAllowed values or usage
partField: string"TIMESTAMP""TIMESTAMP" — Unix timestamp (seconds); "SECOND" — second; "MINUTE" — minute; "HOUR" — hour; "DAY_OF_MONTH" — day of month; "DAY_OF_WEEK" — day of week (1=Mo - 7=Su); "MONTH" — month; "YEAR" — year.
tzField: string"LOCAL""LOCAL" — local; "UTC".

values.customEventScheduled​

Blockly: pl_value_custom_event_scheduled · Kind: value · Output: Boolean.

True while this custom event has a run waiting, including when it is due. False after cancellation or when that run starts. If the handler schedules itself again, the new schedule returns true.

OptionKindField default / input typeAllowed values or usage
eventIdField: string"1""1" — Custom event 1; "2" — Custom event 2; "3" — Custom event 3; "4" — Custom event 4; "5" — Custom event 5.

values.dateTimeTimestamp​

Blockly: pl_value_date_time_timestamp · Kind: value · Output: Number.

Enter a date as YYYY-MM-DD and a time as HH:mm (24-hour). Returns a Unix timestamp in whole seconds. Wearer local uses the configured wearer or lock timezone when the script runs.

OptionKindField default / input typeAllowed values or usage
dateField: stringCurrent editor dateLiteral date text, YYYY-MM-DD.
timeField: string"21:44"Literal text.
tzField: string"LOCAL""LOCAL" — wearer local; "UTC".

values.deviceGet​

Blockly: pl_value_device_get · Kind: value · Output: Any.

Reads the selected device. “Is Cellmate” and “is BeeSting” return true or false and can be used directly in an IF block. They check the model, not its custom name or connection status. “Device type” returns the model ID (cellmate-pro-3 for Cellmate, lockink-aa-a1012 for BeeSting), or empty text when no device is selected. Use “connected” to check the connection separately.

OptionKindField default / input typeAllowed values or usage
keyField: string"NAME""NAME" — name; "TYPE" — device type; "IS_CELLMATE" — is Cellmate; "IS_BEESTING" — is BeeSting; "SUPPORTS_SHOCK" — supports shock; "SUPPORTS_VIBRATION" — supports vibration; "CONNECTED" — connected.

“Supports shock” and “supports vibration” independently return true or false for the selected device and can be used directly in an IF block. They return false when no device is selected or the capability is unavailable; they do not indicate whether the device is connected.

In Code, compare the result with true because values.deviceGet has an Any output type. For example, distinguish vibration-only devices without triggering any device action:

on.manual(() => {
if (values.deviceGet({ key: "SUPPORTS_VIBRATION" }) == true) {
if (values.deviceGet({ key: "SUPPORTS_SHOCK" }) == true) {
ui.text({ text: "Supports vibration and shock" });
} else {
ui.text({ text: "Supports vibration only" });
}
} else if (values.deviceGet({ key: "SUPPORTS_SHOCK" }) == true) {
ui.text({ text: "Supports shock only" });
} else {
ui.text({ text: "Neither capability available" });
}
});

values.diceList​

Blockly: pl_value_dice_list · Kind: value · Output: Array.

A number list containing each saved die value from When dice rolled, ordered by type: d4, d6, d8, d10, d12, d20. Use with list blocks, a Number list variable, or a for each loop. Missing results produce an empty list.

OptionKindField default / input typeAllowed values or usage
playerField: string"PLAYER""PLAYER" — player; "BOT" — bot.

values.diceValue​

Blockly: pl_value_dice_value · Kind: value · Output: Number.

Reads one die from the When dice rolled event. Positions start at 1, ordered by type: d4, d6, d8, d10, d12, d20. For 2d6 + 1d8, positions 1 and 2 are d6 and position 3 is d8. Returns 0 for a missing die or invalid position. In tests, enter player or bot dice values separated by commas.

OptionKindField default / input typeAllowed values or usage
playerField: string"PLAYER""PLAYER" — player; "BOT" — bot.
indexInputNumberSupported value expression.

values.duration​

Blockly: pl_value_duration · Kind: value · Output: Number.

Builds a duration from a numeric amount and unit. Use it for lock time changes and scheduling values. Internally, durations are converted to seconds. You can also provide raw seconds with a Number block.

OptionKindField default / input typeAllowed values or usage
amountField: number1Number; min 0, precision 1.
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.

values.durationFromNumber​

Blockly: pl_value_duration_from_number · Kind: value · Output: Number.

Builds a duration from a Number block and unit. The amount can come from a variable, calculation, or any other Number block. The result is returned in seconds.

OptionKindField default / input typeAllowed values or usage
unitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
amountInputNumberSupported value expression.

values.durationParts​

Blockly: pl_value_duration_parts · Kind: value · Output: Number.

Combines days, hours, minutes and seconds into one duration. For example, 10 days, 12 hours and 30 minutes. The result is returned in seconds.

OptionKindField default / input typeAllowed values or usage
daysField: number1Number; min 0, precision 1.
hoursField: number0Number; min 0, max 23, precision 1.
minutesField: number0Number; min 0, max 59, precision 1.
secondsField: number0Number; min 0, max 59, precision 1.

values.eventVar​

Blockly: pl_value_event_var · Kind: value · Output: Any.

Reads a variable from the current event context. “Triggering extension” returns the extension slug or built-in module ID, such as wheel, dice, task, or public_voting. Direct keyholder or wearer changes leave it empty.

Prefer the scoped event properties documented under each handler. key accepts all 121 raw keys appearing in those tables and the common-event table. Those raw keys are available to the parser; the runtime still needs the matching event context.

OptionKindField default / input typeAllowed values or usage
keyField: string"EVENT_NAME"All raw keys in the event-variable tables; see the note above.

values.formatDuration​

Blockly: pl_value_format_duration · Kind: value · Output: String.

Formats a duration into a user-friendly string. Converts seconds into readable output (for example: '2 days 3 hours'). Useful when showing time values to users.

OptionKindField default / input typeAllowed values or usage
valueInputNumberSupported value expression.

values.formatTimestamp​

Blockly: pl_value_format_timestamp · Kind: value · Output: String.

Converts a Unix timestamp in seconds into text: YYYY-MM-DD HH:mm:ss, YYYY-MM-DD, or HH:mm:ss. Use it in messages, logs, or text variables. Wearer local uses the configured wearer or lock timezone. Invalid values return empty text.

OptionKindField default / input typeAllowed values or usage
formatField: string"DATETIME""DATETIME" — date and time; "DATE" — date; "TIME" — time.
tzField: string"LOCAL""LOCAL" — wearer local; "UTC".
valueInputNumberSupported value expression.

values.listItem​

Blockly: pl_value_list_item · Kind: value · Output: Number.

Reads one number from a list. Positions start at 1. Returns 0 when the position is invalid or missing.

OptionKindField default / input typeAllowed values or usage
indexInputNumberSupported value expression.
listInputArraySupported value expression.

values.listStatistic​

Blockly: pl_value_list_statistic · Kind: value · Output: Number.

Calculates the sum, largest number, smallest number, or number of items in a number list. Empty lists return 0. Supports live list variables and dice results. For example, values.listStatistic({ op: "MIN", list: [6, 2, 6] }) returns 2; with [-6, -2, -8] it returns -8.

OptionKindField default / input typeAllowed values or usage
opField: string"SUM""SUM" — sum; "MAX" — maximum; "MIN" — minimum; "LENGTH" — length.
listInputArraySupported value expression.

values.lockGet​

Blockly: pl_value_lock_get · Kind: value · Output: Number.

Returns lock runtime values. Date values are Unix timestamps in milliseconds. Daily requirements return the configured full-day targets, even when penalties are disabled. They do not subtract completed progress or adjust for partial days. Unset targets return 0.

OptionKindField default / input typeAllowed values or usage
keyField: string"TIME_LOCKED_SECONDS""TIME_LOCKED_SECONDS" — time locked (seconds); "TIME_REMAINING_SECONDS" — time remaining (seconds); "MAX_TIME_REMAINING_SECONDS" — maximum time remaining (seconds); "WEARER_LAST_SEEN_TS" — wearer last seen timestamp; "KEYHOLDER_LAST_SEEN_TS" — keyholder last seen timestamp; "TASK_POINTS" — task points; "DAILY_REQUIRED_TASKS" — tasks required per day; "DAILY_REQUIRED_WRITING_TASKS" — writing tasks required per day; "DAILY_REQUIRED_DICE_ROLLS" — dice rolls required per day; "DAILY_REQUIRED_WHEEL_SPINS" — wheel spins required per day; "DAILY_REQUIRED_CHESS_GAMES" — chess games required per day; "DAILY_REQUIRED_CASINO_GAMES" — casino games required per day; "DAILY_REQUIRED_AI_CHATS" — AI chats required per day; "PARENTAL_CONTROL_SELF_LOCK_REMAINING_SECONDS" — parental control self-lock remaining (seconds); "HARDCORE_MODE_REMAINING_SECONDS" — Hardcore Mode remaining (seconds).

values.lockGetText​

Blockly: pl_value_lock_get_text · Kind: value · Output: String.

Returns lock/wearer/keyholder text fields from session data.

OptionKindField default / input typeAllowed values or usage
keyField: string"LOCK_TITLE""LOCK_TITLE" — lock title; "WEARER_USERNAME" — wearer username; "WEARER_GENDER" — wearer gender; "WEARER_PROFILE_LOCATION" — wearer profile location; "WEARER_ROLE" — wearer role; "WEARER_SEXUAL_ORIENTATION" — wearer sexual orientation; "KEYHOLDER_USERNAME" — keyholder username; "KEYHOLDER_GENDER" — keyholder gender; "KEYHOLDER_PROFILE_LOCATION" — keyholder profile location; "KEYHOLDER_ROLE" — keyholder role; "KEYHOLDER_SEXUAL_ORIENTATION" — keyholder sexual orientation.

values.publicVotingEnabled​

Blockly: pl_value_public_voting_enabled · Kind: value · Output: Boolean.

No options. Whether the feature is enabled and not permanently disabled. Does not by itself establish that voting remains open.

values.publicVotingOpen​

Blockly: pl_value_public_voting_open · Kind: value · Output: Boolean.

No options. Whether voting is enabled, the lock accepts votes, and auto-close has not been reached. Individual account requirements, available choices, and voter cooldowns still apply.

values.publicVotingRequiredVotes​

Blockly: pl_value_public_voting_required_votes · Kind: value · Output: Number.

No options. Configured required total, including while its unlock requirement is disabled; default 10.

values.publicVotingTotalVotes​

Blockly: pl_value_public_voting_total_votes · Kind: value · Output: Number.

No options. Total votes received, retained even when voting is disabled.

values.publicVotingVotesRemaining​

Blockly: pl_value_public_voting_votes_remaining · Kind: value · Output: Number.

No options. Maximum of zero and required minus total votes; zero when voting or the unlock requirement is disabled.

values.randomColor​

Blockly: pl_value_random_color · Kind: value · Output: String / Colour.

Generates a random color value. Use this when you want color output to vary. Returns a hex color string (for example: #3fa9d2).

No options; call values.randomColor().

values.randomDuration​

Blockly: pl_value_random_duration · Kind: value · Output: Number.

Returns a random duration between FROM and TO (inclusive), in seconds. Assign it to a variable once if an action and its message must share the same sampled amount; calling it again can draw a different value. A stored duration uses unit: "SECOND" in generic duration actions. The sampled value is a request amount, not a measurement of the resulting lock change.

OptionKindField default / input typeAllowed values or usage
fromAmountField: number1Number; min 0, precision 1.
fromUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.
toAmountField: number5Number; min 0, precision 1.
toUnitField: string"SECOND""SECOND" — seconds; "MINUTE" — minutes; "HOUR" — hours; "DAY" — days.

values.relativeDateTimeTimestamp​

Blockly: pl_value_relative_date_time_timestamp · Kind: value · Output: Number.

Returns a Unix timestamp for today plus a whole-number day offset at the specified hour and minute. Day offset, hour, and minute can use constants, calculations, or variables. Hours must resolve to 0-23 and minutes to 0-59.

OptionKindField default / input typeAllowed values or usage
tzField: string"LOCAL""LOCAL" — wearer local; "UTC".
dayOffsetInputNumberSupported value expression.
hourInputNumberSupported value expression.
minuteInputNumberSupported value expression.

text APIs​

text.append​

Blockly: text_append · Kind: statement.

Appends text to the end of a selected variable. The variable is updated in place.

The var option takes a declared variable identifier. Prefer a string variable.

OptionKindField default / input typeAllowed values or usage
varVariable selectorDeclared identifierChoose a declared variable; no quoted name or generated ID.
textInputAnySupported value expression.

text.indexof​

Blockly: text_indexOf · Kind: value · Output: Number.

Finds where one text appears inside another. Can search first or last occurrence. Returns 1-based position, or 0 if missing.

OptionKindField default / input typeAllowed values or usage
endField: string"FIRST""FIRST" — first; "LAST" — last.
valueInputStringSupported value expression.
findInputStringSupported value expression.

text.join​

Blockly: text_join · Kind: value · Output: String.

Combines text, variables, and other values in order. Change the parts count to add or remove inputs.

With partCount: N, use add0 … addNminus1 (zero-based). Compact text(a, b, ...) is equivalent.

OptionKindField default / input typeAllowed values or usage
partCountField: number2Number; min 2, max 32, precision 1.
add0InputAnySupported value expression.
add1InputAnySupported value expression.

text.length​

Blockly: text_length · Kind: value · Output: Number.

Counts characters in a text value. Returns the character length as a number.

OptionKindField default / input typeAllowed values or usage
valueInputString / ArraySupported value expression.

text.replace​

Blockly: text_replace · Kind: value · Output: String.

Replaces every matching segment in a string. Useful for substitutions and corrections. Search is case-sensitive.

OptionKindField default / input typeAllowed values or usage
fromInputStringSupported value expression.
toInputStringSupported value expression.
textInputStringSupported value expression.

variables APIs​

variables.get​

Blockly: variables_get · Kind: value · Output: Any.

Reads a variable's current value. Use it anywhere you need stored data in a condition, calculation, or action.

Usually write the declared variable identifier directly. The generic var option also takes an identifier.

OptionKindField default / input typeAllowed values or usage
varVariable selectorDeclared identifierChoose a declared variable; no quoted name or generated ID.

variables.set​

Blockly: variables_set · Kind: statement.

Stores a value in a variable. Use this to remember data and update it as your program runs.

Usually write variable = expression;. The generic var option takes an identifier.

OptionKindField default / input typeAllowed values or usage
varVariable selectorDeclared identifierChoose a declared variable; no quoted name or generated ID.
valueInputAnySupported value expression.

blocks APIs​

blocks.ifDo​

Blockly: pl_if_do · Kind: statement.

Runs the body only when its condition is true.

OptionKindField default / input typeAllowed values or usage
condInputBooleanSupported value expression.
doBranch() => { ... }May instead be the trailing callback.

blocks.ifElse​

Blockly: pl_if_else · Kind: statement.

Run the first matching branch, then skip the remaining else-if and else branches. Supports up to 50 else-if branches.

For N else-if branches, set elseIfCount: N, then use elseif0Cond, elseif0Do … through index N−1. Prefer native if / else if / else for new code.

OptionKindField default / input typeAllowed values or usage
elseIfCountField: number0Number; min 0, max 50, precision 1.
condInputBooleanSupported value expression.
doBranch() => { ... }May instead be the trailing callback.
elseBranch() => { ... }Named branch in the options object.

blocks.ifLockFrozen​

Blockly: pl_if_lock_frozen · Kind: statement.

Chooses a branch from the lock's frozen state.

OptionKindField default / input typeAllowed values or usage
doBranch() => { ... }May instead be the trailing callback.
elseBranch() => { ... }Named branch in the options object.

blocks.ifRandomChance​

Blockly: pl_if_random_chance · Kind: statement.

Makes a percentage-based choice between two action branches. Use 0 for never and 100 for always.

OptionKindField default / input typeAllowed values or usage
chanceInputNumberSupported value expression.
doBranch() => { ... }May instead be the trailing callback.
elseBranch() => { ... }Named branch in the options object.

blocks.randomOutcome​

Blockly: pl_random_outcome · Kind: statement.

Create up to 50 weighted branches and pick one at runtime. Each branch has a positive weight. Selection chance is weight divided by total weight. Only the chosen branch executes.

Inside outcomes, add blocks.randomOutcomeOption statements. outcomeCount is the branch-count field; it does not create code callbacks automatically.

OptionKindField default / input typeAllowed values or usage
outcomeCountField: number2Number; min 2, max 50, precision 1.
outcomesBranch() => { ... }Named branch in the options object. Blockly statement check: PL_OUTCOME.

blocks.randomOutcomeOption​

Blockly: pl_random_outcome_option · Kind: statement.

One weighted outcome branch inside Random outcome.

Place inside blocks.randomOutcome({ outcomes: () => { ... } }). Its positive weight is relative to the sum of all option weights.

OptionKindField default / input typeAllowed values or usage
weightField: number1Number; min 0.0001, precision 0.0001.
doBranch() => { ... }May instead be the trailing callback.

blocks.text​

Blockly: text · Kind: value · Output: String.

A letter, word, or line of text.

OptionKindField default / input typeAllowed values or usage
textField: string""Literal text.

globals APIs​

These blocks use the current wearer's group connection. The owner manages definitions and connections outside code. All four use a literal key field (default "score"); they do not take a group ID or wearer ID.

globals.connected​

Blockly: pl_global_connected · Kind: value · Output: Boolean.

globals.connected({ key: "score" }) returns whether this variable exists and its connection is enabled. Use it in a condition before optional access. A valid connected Boolean whose value is false is still connected.

globals.connectedCount​

Blockly: pl_global_connected_count · Kind: value (Number).

globals.connectedCount({ key: "score" }) returns an integer count of distinct active wearers with this variable enabled in the caller's group, including the caller. The literal key field defaults to "score". Missing/disabled connections fail; guard optional access with globals.connected. It works in calculations, function arguments, and automatic inputs. Counts do not combine different groups and are not subscriptions. The corresponding event is on.globalVariableConnectionsChanged.

globals.get​

Blockly: pl_global_get · Kind: value · Output: the variable's declared type.

globals.get({ key: "score" }) reads the current shared value. Missing/disabled connections fail. Can be used in calculations, text, function arguments, and automatic inputs. It is a snapshot, not a subscription.

globals.increment​

Blockly: pl_global_increment · Kind: statement.

globals.increment({ key: "score", value: 1 }) atomically adds its value expression to a Number or Integer variable. Negative deltas subtract. The result must satisfy the variable's type and numeric limits. Emits a change event only when the value changes.

globals.set​

Blockly: pl_global_set · Kind: statement.

globals.set({ key: "score", value: 10 }) replaces the shared value with the value expression, enforcing its type. Emits a change event only when the value differs. Both write blocks require event execution; returning functions used for automatic controls cannot contain them.