JavaScript Scripts

Custom JavaScript functions extend the form where rules are not enough. Scripts must be enabled by a tenant administrator.

A script is JavaScript that runs in the browser of every user who opens the form, with that user's permissions, and is not sandboxed. Anyone allowed to modify the form configuration can execute arbitrary code. Use scripts only when rules and expressions are insufficient, and review the code carefully.

When to use a script

A script is a last resort. Try these tools first, because they are safer, do not require tenant-level enablement, and can be validated by the designer:

Scripts are suitable for complex logic (loops, multiple conditions, working with arrays and objects), custom messages based on form state, and checks that cannot be expressed as rule conditions.

Script policy (tenant enablement)

Scripts are disabled by default. A tenant administrator enables them once for all sites (tenant Storage Entity FormCraft365_ScriptsPolicy = allow; the legacy key SpfxFormCustomizer_ScriptPolicy = enabled is only read when the new key does not exist, and deny in the new key always takes precedence). A missing or unknown value means disabled. Details: Script Policy.

Form opened by a designer in a tenant without scripts enabled: info bar about disabled scripts at the top of the form

Script editor

Without a Pro license, the editor is read-only: an explanation with a Pro badge is shown at the top, existing scripts can be viewed but not edited, and new functions cannot be added. Scripts still run (only the script policy determines execution). See Licensing.

In the designer, open the Scripts tab. It contains three sections and a reference panel for ctx:

Click Test syntax to verify that the code compiles. Write the code as a function body, not a declaration: do not wrap it in function name() {…}. The body runs in strict mode as an async function, so you can use await and return. The only parameter is ctx.

Script editor: onFormLoad and onBeforeSave fields, list of custom functions, and ctx reference with the Test syntax button

Load and before-save scripts

onFormLoad

Runs once after the form loads (and again if the form mode or rule configuration changes). Useful for a welcome message, pre-filling values, or initially hiding fields. The form does not wait for this script and renders immediately. Any return value is ignored.

onBeforeSave

Runs on Save after field validation and after onSave rules. Its return value determines whether saving proceeds:

if (!ctx.getValue("Cena")) return "Please enter the price before saving.";
const total = Number(ctx.getValue("Cena")) * Number(ctx.getValue("Kusy") || 1);
if (total > 100000 && !ctx.getValue("Schvalovatel")) {
  return "Orders over 100,000 must have an approver.";
}

Named functions

You define functions in the editor and call them using the callFunction action in a rule (target $script) or the callFunction action on a button. In the designer, the rule lets you pick from defined function names; configuration validation rejects references to non-existent functions. The function's return value is ignored. A function called from an always rule runs when its conditions become true or their values change, up to 10 times (loop protection).

{
  "scripts": {
    "functions": [
      {
        "id": "fn-dph",
        "name": "prepocitejDph",
        "description": "Calculates price including VAT",
        "code": "const bez = Number(ctx.getValue('CenaBezDph')) || 0;\nctx.setValue('CenaSDph', Math.round(bez * 1.21 * 100) / 100);"
      }
    ]
  }
}

Init vs. user phase: when the form is "dirty"

Scripts run in two phases, which affects how ctx.setValue behaves:

PhaseWhenDefault shouldDirty for ctx.setValue
initonFormLoad, functions called from onLoad rules, and the initial rule evaluation on openfalse: the form is not marked as modified
useronBeforeSave, functions from onSave rules, buttons, and subsequent field changestrue: the form is marked as modified

Calling from rules and buttons

Async code, errors, and debugging

Script examples

Welcome message by mode (onFormLoad)

if (ctx.formMode === "new") {
  ctx.notify("Welcome, " + (ctx.currentUser?.title || "") + ". Please fill in all required fields.", "info");
}

Pre-filling a value on a new item (onFormLoad)

if (ctx.formMode === "new" && !ctx.getValue("Oddeleni")) {
  ctx.setValue("Oddeleni", "Sales");   // init phase: form remains clean
}

Hiding a section by group (onFormLoad)

const groups = (ctx.currentUser?.groups || []).map(g => g.toLowerCase());
if (groups.indexOf("approvers") < 0) {
  ctx.setVisible("section:sec_schvaleni", false);
}

Recalculation on button click (named function)

const bez = Number(ctx.getValue("CenaBezDph")) || 0;
ctx.setValue("CenaSDph", Math.round(bez * 1.21 * 100) / 100);
ctx.notify("Price including VAT has been recalculated.", "success");

Validation before save (onBeforeSave)

const from = ctx.getValue("Od");
const to = ctx.getValue("Do");
if (from && to && String(to) < String(from)) {
  return "The 'To' date cannot be earlier than the 'From' date.";
}

Security notice

A script has the same permissions as the signed-in user; it can read and modify data, call SharePoint, and call other services. Enabling scripts applies to the entire tenant.

Context API ctx

Every script receives the ctx object. Read-only members cannot be modified. The target parameter for setVisible and setReadOnly is a field internal name, section:<section id>, or tab:<tab id>. States set by a script reset to their defaults when the form mode or rule configuration changes.

ctx.formMode

Type: "new" | "edit" | "display" (read-only). Current form mode: new item, edit, or display.

if (ctx.formMode === "display") return;

ctx.itemId

Type: number | undefined (read-only). ID of the item being edited. For a new item (not yet saved), it is undefined.

if (ctx.itemId) console.log("Editing item", ctx.itemId);

ctx.locale

Type: string | undefined (read-only). Code of the currently selected form language (cs, en, de, sk). Updates when the user switches languages.

ctx.notify(ctx.locale === "en" ? "Saved draft" : "Koncept uložen", "info");

ctx.currentUser

Type: { id?, email?, loginName?, title?, groups? } | undefined (read-only). Signed-in user: ID, email, login name, display name, and names of SharePoint groups they belong to (groups).

const isApprover = (ctx.currentUser?.groups || []).indexOf("Approvers") >= 0;

ctx.getValue

Type: (fieldName: string) => unknown. Returns the current value of a field by its internal name. The value type depends on the field: string, number, boolean, date as string, or an object / array of objects for person, lookup, and managed metadata fields. Non-existent fields return undefined.

const price = Number(ctx.getValue("Cena")) || 0;

ctx.getAllValues

Type: () => Record<string, unknown>. Returns a copy of all current form values (modifying the copy does not affect the form).

const all = ctx.getAllValues();
console.log(Object.keys(all).length + " fields");

ctx.setValue

Type: (fieldName: string, value: unknown, options?: { shouldDirty?: boolean; shouldValidate?: boolean }) => void. Sets a field's value. shouldValidate defaults to true (the field is validated immediately). shouldDirty depends on the phase: false in init, true in user, and always false in display mode. Pass the value in the shape the field expects (number, string, or an object with id and title for person or lookup fields).

ctx.setValue("Stav", "Approved");
ctx.setValue("Poznamka", "", { shouldDirty: true });

ctx.setVisible

Type: (target: string, visible: boolean) => void. Shows or hides a field, section (section:id), or tab (tab:id). The script overrides the rule-derived state for that target.

ctx.setVisible("tab:tab_priloha", false);

ctx.setRequired

Type: (fieldName: string, required: boolean) => void. Sets the required state of a field (fields only, not sections or tabs).

ctx.setRequired("DuvodZamitnuti", ctx.getValue("Stav") === "Rejected");

ctx.setReadOnly

Type: (target: string, readOnly: boolean) => void. Locks or unlocks a field, section (section:id), or tab (tab:id). In display mode, all fields remain read-only.

ctx.setReadOnly("section:sec_cena", true);

ctx.setCollapsed

Type: (sectionId: string, collapsed: boolean) => void. Collapses or expands a section. You may pass the ID with or without the section: prefix. Works on sections configured as collapsible; the user can toggle it manually afterward.

ctx.setCollapsed("sec_prilohy", true);

ctx.notify

Type: (message: string, intent?: "info" | "success" | "warning" | "error") => void. Displays a banner message at the top of the form. The default intent is info. Only one message is shown at a time: a new one replaces the previous one, and the user can dismiss it.

ctx.notify("Warning: the due date is in the past.", "warning");