Custom code with AI
FillFaster forms can run custom code. Use it for rules the form builder does not cover, for example:
- Show fields only when a checkbox is checked or a question is answered Yes.
- Make fields required only while they are shown.
- Show extra fields for some options of a list.
- Fill one field from another, or lock a field that was filled in advance.
- Check an answer with your own rule, such as a minimum age.
You do not need to write the code yourself. An AI assistant (ChatGPT, Claude, Gemini, Cursor and others) can write it from your form link and your conditions.
Ask an AI assistant to write the code
Section titled “Ask an AI assistant to write the code”Copy this prompt, fill in your form link and conditions, and send it to the assistant:
Fetch and follow https://help.fillfaster.com/.well-known/agent-skills/fillfaster-custom-code/SKILL.md to write custom code for my FillFaster form.Form link: https://fillfaster.com/fill/<form id>Conditions:- <for example: show "Spouse name" and "Spouse ID" only when "Married" is checked, and make them required>The assistant reads your form’s real fields, asks about anything unclear, and returns one block of code with a short list of the rules it covers. If your form already has custom code, it explains what that code does and asks whether to add to it or rewrite it.
Paste the code into the form
Section titled “Paste the code into the form”- Open the form in the form builder.
- Go to Settings, then Advanced Settings.
- Paste the code into Custom HTML Code (replace what was there if the assistant rewrote it).
- If the assistant used fields that are required only when shown, set those fields to not required in the builder, as it tells you.
- Save the form.
Forms this does not work for
Section titled “Forms this does not work for”Forms with an access code, and templates with several signers, cannot be read this way. For these forms, contact FillFaster support at support@fillfaster.com and include the form id.
Reference for AI agents
Section titled “Reference for AI agents”The rest of this page is the reference the assistant follows. It is also available as plain Markdown at custom-code-reference.md.
Custom Code: window.ffFormAPI
Overview
FillFaster forms can run custom HTML/JavaScript from the form settings (htmlCode). That code runs in the filler’s browser, typically to show, hide or require fields based on other answers.
Hiding a field with CSS does not change its validation. A field marked required: true still blocks submission when hidden.
window.ffFormAPI is a small, stable bridge from custom code into the live form. It lets form scripts show and hide fields, toggle required state, read and write values, run validity checks and add validators.
Availability
| Condition | window.ffFormAPI |
|---|---|
Form has custom code (htmlCode is not empty) |
Defined, for the current form |
| Form has no custom code | Not defined |
| Form reset (after a submit or a created link) | Re-attached to the new form |
Custom code lifecycle
- The form loads and
ffFormAPIis attached. - The
<script>and<style>elements of the custom code are added to the page. - When the questions are rendered, the page posts
window.postMessage({ action: 'form_questions_loaded' }).
New code: put everything inside ffFormAPI.ready(...) (see Conditional logic helpers). It waits for that message, and runs right away if it already arrived:
window.ffFormAPI && window.ffFormAPI.ready(function () { // Your logic here});Older code waits for the message itself. It keeps working:
window.addEventListener('message', function (event) { if (event.data && event.data.action === 'form_questions_loaded') { if (!window.ffFormAPI) { console.error('ffFormAPI is not available on this form.'); return; } // Your logic here }});Field keys vs row ids
| Purpose | Identifier | Example |
|---|---|---|
ffFormAPI methods |
Field key (the field’s name) |
field48, field10 copy |
| The field’s row on the page | Row id on .form-row |
field-field48, field-field10 copy |
Pattern: row id = field- + field key. In new code use ffFormAPI.getRow(key) instead of building the id.
Writing custom code (agents and developers)
1. Get the real fields first
GET https://api.fillfaster.com/v1/form/{fid}/custom-code-contextNo token. fid is the id in the form link (fillfaster.com/fill/{fid}). Returns { fid, formName, lang, schemas, htmlCode }: the form’s fields and its current custom code. Forms with an access code or 2+ signers return 403 form_not_public; ask the customer to contact FillFaster support (support@fillfaster.com) with the form id.
Never write a field key that is not in this response. If a field the customer describes is missing or ambiguous, ask which field they mean (list fieldName + name). Do not guess.
2. Which entries are fields
schemas is one array per PDF page. Each entry’s name is the field key and fieldName is the label the filler sees. The form turns them into questions like this:
- Same label, one field. Entries that share a
fieldName(the same answer printed in several places) become one question, keyed by the first entry’sname. Use that key. - No row:
type: 'helper'entries, and anything without avalidationType. - Row, always hidden:
validationType: 'date_today'and entries withhideInput: true(filled by the sender or prefill). - Row, no value:
validationType: 'html'/'external_link'(static text).showFields/hideFields/getRowwork on them; value and validation methods do not. multi_variable_text: one text field per variable, keyed{name}_{variable}, labelled with the variable name.- Value types: checkbox
true/false; yes/no the translated word (useisYes/isNo); select and radio the option text; date aDate; file/image an array; table an array of row objects. requiredis the starting state. For a field that is required only when shown, set it to not required in the form builder and useshowWhen(..., { required: true }).
3. Pick the helper for the use case
| Customer asks | Write |
|---|---|
| Show fields when a checkbox is checked | api.showWhen('cb', true, [...]) |
| Show fields when a yes/no answer is Yes (or No) | api.showWhen('q', true, [...]) / api.showWhen('q', false, [...]) |
| …and make them required while shown | add { required: true } |
| Show fields for some select / radio options | api.showWhen('sel', ['A', 'B'], [...]) |
| Show fields when a field has any value (date, text) | api.showWhen('f', function (v) { return !!v; }, [...]) |
| Show the next group once the previous one is filled (child 1, 2, 3…) | chain showWhen from a key field of each group to the next group |
| Fill one field from another (select → code) | `api.onValueChange(‘sel’, function (v) { api.setValue(‘code’, MAP[v] |
| Only one of several checkboxes | onValueChange on each; when one turns true, setValue(other, false) on the rest |
| Lock a prefilled field | api.setReadOnly('f', true) |
| Custom validation (age, unique ID, phone) | api.addValidator('f', fn); see addValidator, and revalidateFields(..., { markTouched: 'invalid' }) for cross-field rules |
| A “continue” button that opens a section only when fields are valid | areFieldsValid + revalidateFields(..., { markTouched: true }), then showFields |
Everything goes inside one ready call:
<script>window.ffFormAPI && window.ffFormAPI.ready(function () { var api = window.ffFormAPI; // One line per rule. Comment each rule with the labels it uses.});</script>4. Rules
- Read and write values only through
ffFormAPI, never throughinput.value,dispatchEvent, widget CSS classes,MutationObserver, label text orlocalStorage. The page markup is not part of the contract and has changed before. - Never compare yes/no answers to
'כן'/'Yes'. UseisYes/isNoorshowWhen(key, true|false, ...). - Do not hide rows in CSS;
showWhen/hideFieldshide them on load. CSS is for layout and styling only. - No
setTimeoutto wait for the form.readyalready waits. - Keep everything inside the
readycallback so nothing leaks into globals. - Custom code is not a security boundary. It runs in the filler’s browser.
- Multiple signers: when a template with several signers has custom code, the server checks only signature fields as required for each signer. Required rules for other fields are enforced by the form only.
5. When the form already has custom code
htmlCode in the context response is the code on the form now.
- Read it and list, in plain words, what each part does.
- Ask the customer: add the new rule and keep the existing code as it is, or rewrite the code to the helpers above (same behavior, shorter, and it keeps working when the form changes)?
- If rewriting: keep every rule’s behavior, map each old pattern to its helper (table below), and keep anything that has no helper (custom CSS, signature-pad tweaks, iframes) unchanged.
- Return the full new code, not a diff, with a short list of what changed.
| Old pattern | Helper |
|---|---|
window.addEventListener('message', ... 'form_questions_loaded') |
ffFormAPI.ready(fn) |
showHideListOfFields([...rows], 'show'/'hide') + setRequired + setValue(key, '') |
showFields / hideFields(keys, { clear: true }) |
input.checked, radio value === 'כן', getValue(key) === 'Yes' |
isYes(key) / showWhen(key, true, ...) |
MutationObserver or click listeners on a select’s markup |
onValueChange(key, fn) |
input.value = x; input.dispatchEvent(new Event('input')) |
setValue(key, x) |
document.getElementById('field-' + key) |
getRow(key) |
input.readOnly = true |
setReadOnly(key, true) |
| Finding fields by label text | the name from the context response |
6. What to hand back
- One block to paste into the form builder: Settings → Advanced Settings → Custom HTML Code (Hebrew UI: “הגדרות מתקדמות” → “קוד HTML מותאם אישית”):
<script>(and<style>only if needed). - Under it, a short list: each rule in the customer’s words, with the field labels it uses.
- Tell the customer to set the conditional fields to not required in the builder when you used
{ required: true }, and to test the form link before sending it.
Patterns without a helper
Hide “Receive a copy by email” on the success screen, keep Download (CSS on a page class; re-check if the success screen changes):
<style>.get-submitted-pdf-container > *:nth-child(n+4) { display: none; }</style>Dynamic checkbox list written into a text field (options depend on a select or a table): build the checkboxes inside getRow(textKey), hide the text input, and on every change api.setValue(textKey, checked.join(', ')).
Signature pad size or colour: possible, but it depends on page internals and can break when the form changes. Write it only when the customer asks, and test on mobile.
Embedding the form in an iframe and listening to its events is not custom code; it is the embed flow (window.parent.postMessage with status: 'submitted_successfully').
API reference
All methods log a console warning and return false when the field key is missing (unless noted).
setRequired(fieldKey, isRequired)
Makes a field required or optional. Its other checks (ID number, phone, email, pattern) stay as they are. The asterisk next to the label follows the change.
window.ffFormAPI.setRequired('field48', true); // make requiredwindow.ffFormAPI.setRequired('field48', false); // make optionalReturns: boolean: whether the field was found.
areFieldsValid(fieldKeys)
Returns true only if every listed field is currently valid. Missing fields are treated as valid.
var ok = window.ffFormAPI.areFieldsValid(['field1', 'field2', 'field3', 'field4']);if (!ok) { window.ffFormAPI.revalidateFields(['field1', 'field2', 'field3', 'field4'], { markTouched: true });}Returns: boolean
revalidateFields(fieldKeys, options?)
Re-runs the checks on the listed fields.
options.markTouched |
Behavior |
|---|---|
omitted / false |
Revalidate only |
true |
Revalidate and show errors on all listed fields |
'invalid' |
Revalidate all; show errors only on invalid fields |
Use 'invalid' for cross-field rules (e.g. uniqueness) so sibling fields refresh without marking valid ones.
// Cross-field uniqueness: refresh siblings, show errors only on duplicateswindow.ffFormAPI.revalidateFields(['field2', 'field10', 'field10 copy'], { markTouched: 'invalid' });
// Gate a section: show errors on every invalid field in the listwindow.ffFormAPI.revalidateFields(['field1', 'field2', 'field3', 'field4'], { markTouched: true });Returns: void
getValue(fieldKey)
Reads the field’s current value (e.g. Date for date fields, string for text).
Returns: the value, or undefined if not found.
setValue(fieldKey, value)
Sets the value, marks the field as touched, and re-runs its checks. Use when clearing a hidden section (e.g. after removing a dynamic row).
window.ffFormAPI.setValue('field12', '');Returns: boolean: whether the field was found.
addValidator(fieldKey, validatorFn)
Adds a check from custom code.
validatorFn(value)must returnnull(valid) or a string error message (invalid). The message is shown under the field.
window.ffFormAPI.addValidator('field12', function (value) { if (!value) return null; // let required handle empty var d = new Date(value); if (d > new Date()) return 'לא ניתן לבחור תאריך עתידי'; // ... age check, etc. return null;});Note: Each call adds another check. Register once per field.
Returns: boolean: whether the field was found.
Conditional logic helpers
These cover most custom code: show, hide and require fields by another field’s value. They read the form’s values, not the page markup, so they keep working when the form’s look changes. Prefer them over DOM listeners, MutationObserver, label-text lookups and setTimeout.
Compatibility contract: custom code on live forms calls these names. Only add methods or options. Never rename one or change what it does.
ready(cb)
Runs cb once the questions are rendered, or right away if they already are. Call every other helper inside it.
onValueChange(fieldKey, cb)
Calls cb(value) now with the current value (prefill included) and again on every change. Listeners stay active when the form resets after a submit or a created link, and rerun on the new form. Returns: boolean.
showFields(fieldKeys, options?) / hideFields(fieldKeys, options?)
Show or hide the rows (#field-{key}) and keep validation in step:
- Hiding removes
required, so a hidden field never blocks submit. Showing it again restoresrequiredif it had it. showFields(keys, { required: true })also makes the fields required (not required in the builder + shown = required).hideFields(keys, { clear: true })empties the hidden fields. It does not clear while rules are first applied on load, so prefilled values survive.
Showing removes the row’s inline display style, so fields hidden in the form builder stay hidden. Do not hide rows with CSS in custom code: a CSS-hidden row stays hidden after showFields. Let showWhen / hideFields hide them on load.
Keys of headings or static text have a row and no value; they are shown and hidden like any field.
showWhen(triggerKey, condition, fieldKeys, options?)
Shows fieldKeys while triggerKey’s value matches condition, hides them otherwise. Built on onValueChange, so it runs on load and after every change.
condition |
Matches when |
|---|---|
true |
yes/no is Yes, or checkbox is checked (isYes) |
false |
yes/no is No, or checkbox is unchecked (isNo). Unanswered yes/no matches neither |
a value, e.g. 'Married' |
value equals it (select, radio, text) |
| an array of values | value is one of them |
function (value) { ... } |
the function returns truthy |
options: { required: true } as in showFields; clear defaults to true (hidden answers are not submitted). Pass { clear: false } to keep them. Returns: boolean (trigger found).
isYes(fieldKey) / isNo(fieldKey)
Yes/no answers are stored as the translated word ('Yes' in English, 'כן' in Hebrew), and checkboxes as true/false. Comparing to a literal word breaks when the form language changes. Use these instead; they accept both forms in any language. A yes/no question nobody answered is neither yes nor no.
setReadOnly(fieldKey, readOnly)
Locks or unlocks a field. The value is still submitted, and a locked field is not validated. Stays locked after the form resets. Returns: boolean.
getRow(fieldKey)
Returns the field’s row element (#field-{key}) or null. Use it instead of building the id yourself when you need the page element, e.g. to insert a button after a row.
Example
window.ffFormAPI && window.ffFormAPI.ready(function () { var api = window.ffFormAPI;
// Married checkbox → spouse details, required while shown api.showWhen('field39', true, ['field24', 'field25'], { required: true });
// Yes/no "has a third party?" → third-party details api.showWhen('field41', true, ['field3 copy', 'field5 copy', 'field12']);
// Select → extra fields for two of its options api.showWhen('field15', ['Purchase', 'Sale'], ['field74', 'field82']);
// Prefilled vehicle number stays as sent api.setReadOnly('vehicle_number', true);});Typical patterns
Gate a section until employee fields are valid
continueBtn.addEventListener('click', function () { var keys = ['field1', 'field2', 'field3', 'field4']; if (!window.ffFormAPI.areFieldsValid(keys)) { window.ffFormAPI.revalidateFields(keys, { markTouched: true }); return; } window.ffFormAPI.showFields(['field48', 'field10', 'field11'], { required: true });});Security and scope
- The API exists only on forms whose owner added custom code.
- It works on the current form only.
- It does not bypass server-side checks; anything security-critical must be checked on the server.