Changelog

Release notes for the FieldScroll platform, published per release.

Updated 2026-09-06

Release notes for the FieldScroll platform. Entries are published per release — when something ships, it gets a dated entry here. There's no fixed weekly or monthly cadence; quiet periods mean nothing shipped, not that this page was forgotten.

September 6, 2026

A report can now file its finished document into Box. Alongside email and webhook, a report destination can be a folder in your Box account. Save the destination, then use Authorize Box on the report's page: sign-in happens on Box's own consent screen, so FieldScroll never sees your Box password, and the tokens are stored encrypted. A Box administrator may need to enable the FieldScroll app once before people in the company can authorize it.

Once authorized, pick the base folder. Documents can all land there, or land in a subfolder path built from merge fields — for example Accounts/{$data.record.field_values.account}/Reports — where missing levels are created and existing ones reused. A merge field can also carry the Box folder id directly. An optional file description accepts merge fields too, and the editor previews both the folder path and the description against sample data, naming any field with no value.

When a file of the same name is already in the folder, the destination either keeps both — the new file gets a short suffix — or replaces it by uploading a new version of the same Box file, so its id and sharing stay intact. Keeping both is the default.

A Box destination can call you when the file lands. Give it a URL and it receives a report.delivery.succeeded event once the upload is confirmed: the Box file id, folder path and link, the file's name, size and checksum, and the data the document was merged from. It arrives in the same signed envelope as every other webhook, so an existing receiver verifies it with the code you already have. The callback shows as its own row in the delivery history and can be sent again from there. You can also list addresses to email when a Box delivery gives up, so a failure reaches a person rather than sitting in a table.

document.rendered now arrives in the standard webhook envelope. A report's webhook destination used to POST the document details on their own. They now sit in the same id / event / timestamp / organization_id / data envelope every other webhook uses, so one receiver handles report documents and record events together. Neither report-delivery event is something you subscribe to — each goes to the URL set on the destination. Reports has both payloads.

Delivery history shows more, and retries more. Deliveries now show a needs attention state, used for exactly one case: a destination set to replace an existing Box file found that the file changed in Box between being read and being written, so nothing was overwritten. Check the file, then retry. A delivery marked failed or needs attention can be retried from the history, and a Box delivery that succeeded links straight to the file in Box.

A report can also file its document into Google Drive. A destination can be a folder in a person's My Drive or in a shared drive. It is set up the same way as Box: save the destination, then use Authorize Google Drive on the report's page and sign in on Google's own consent screen. Until Google finishes reviewing the FieldScroll app, that screen shows an "unverified app" notice; continue through Advanced to grant access. A Google Workspace administrator may need to allow the app first. The folder picker starts in My Drive and lists every shared drive the account can reach. Subfolder paths built from merge fields, a folder id carried in a merge field, the file description, the callback URL, and the error email all work as they do for Box.

Drive handles same-name files differently. A Drive folder can hold several files with one name, so there is no suffix. With replace off, the delivery adds a new file beside the old one. With replace on, it uploads a new revision of the most recently changed file of that name, so the file id and its sharing stay intact, and Google keeps earlier revisions for a while in Drive's revision history. The needs attention state is Box only; a Google Drive delivery that runs into trouble is marked failed.

Signing secrets are write-only. The secret on a webhook destination or a Box callback cannot be read back after it is saved — not in the dashboard, not through the API, and not by any query a signed-in browser session can make. Both show only whether a secret is set. Leave the field blank to keep the stored secret, or type a new value to replace it.

September 3, 2026

A data source can now mirror a Salesforce object. Until now a managed data source meant a table or view in your own PostgreSQL or MySQL database. Data sources → New now offers a third choice, Managed (Salesforce): sign in to your Salesforce org, pick an object, choose the fields to mirror, and set how often to sync. The rows land as an ordinary data source, so lookup fields read them, they download to phones for offline collection, and the API returns them like any other rows. Rows stay read-only in FieldScroll — Salesforce stays the book of record.

Sign-in happens on Salesforce's own page, so FieldScroll never sees your password, and the tokens are stored encrypted. You choose production or Developer Edition against sandbox when you connect. Sync runs every 60 minutes by default and no more often than every 15, because Salesforce counts API calls against a daily limit for your org.

Salesforce drift is handled differently from a database source, on purpose. A mapped field that disappears from Salesforce is kept rather than dropped, until no published form points at it. New Salesforce fields are not added on their own — you add them when you want them. And a mapped field that is re-typed in a way that would change the column's type stops the sync with an error naming the field, instead of quietly changing a column that live forms already read. The data sources page covers all of this.

Every managed source now has a sync panel. Both kinds — database and Salesforce — show status, the time of the last sync, the interval, what the source mirrors and from where, and the full text of the last error, on the source's own page instead of only as a badge in the list. Sync now runs one immediately. A Salesforce source also gets Reconnect Salesforce for when tokens expire, access is revoked, or a sandbox refresh moves the org.

REST API. POST /organizations/{id}/data-sources accepts source_type: "salesforce" with a salesforce_config block and OAuth token credentials, and source_type on a response may now read salesforce. Managed sources still reject row writes through the API, whichever system they mirror.

September 1, 2026

Your branding now shows before anyone signs in. The dashboard's sign-in page, the invitation page, and the two password-reset pages read your workspace's app name, logo, and colors, and the browser tab shows your icon and your name. Until now those pages could only be read by a signed-in session, so a person who had not signed in yet — which is everyone on the sign-in page — saw the default FieldScroll name and mark instead of yours. That was the one place in the dashboard where a white-labeled workspace still looked like ours. Nothing to change on your side: the values you already saved in instance settings are what these pages read, and they apply on the next page load.

Only branding is readable this way. App name, short app name, the three colors, the logo, and the icon. The rest of instance settings — the support email, SMTP host and credentials, storage configuration — still requires a signed-in session, and none of it is served to an anonymous visitor.

A miscount in the docs, corrected. Two pages said three MCP tools can write. Seven can: four that author and release forms, two that write records, and one that creates a webhook subscription. The MCP server page has listed all seven since they shipped, but API keys and the API overview still carried the old number, which understates what a key with write scopes can do. If you granted forms:write or records:write on the strength of the smaller number, the MCP server page is the accurate list.

The public website gained solution and migration pages. Nothing in the product changed. If you are explaining FieldScroll to somebody, or weighing a move from another platform, the new pages at fieldscroll.com/solutions cover white-label deployment and offline capture through to reporting, and two more explain what a dedicated instance means for your data and what moving from iFormBuilder involves.

August 16, 2026

Submitted records upload on their own. Until now a submitted record sat on the phone until somebody tapped the sync icon, and nothing on screen said so — a crew could finish a day of work believing it had been sent. Submitting now starts the upload immediately when the phone has a connection, and opening the app with records still waiting uploads them too. The sync icon carries an amber dot for as long as anything is waiting, and a submitted record in the Records list shows an upload-pending cloud icon instead of a checkmark, because submitted means queued, not delivered. Only synced means the data has reached the platform. The sync icon and pull-to-refresh still work as a manual fallback. See offline and sync.

Password recovery on the phone. The mobile sign-in screen now has Forgot your password?. Type your email, tap it, and a reset link arrives by email; you set the new password in the phone's browser and return to the app to sign in. Workspaces that sign in through their own identity provider do not see the link, because those accounts have no FieldScroll password to reset.

Leaving a form saves the same way every time. Swiping back or using the phone's own back gesture skipped the save-and-cleanup that the header back button ran, so an edit made after the last auto-save could be dropped. Every exit path now runs the same steps. If the save cannot be written — a full device, usually — the app says so and keeps you in the form instead of closing it.

A rule that names a deleted field is caught before it ships. Deleting a field does not delete the formulas and Visible when rules that read it. Left alone, the common case hid the field carrying the rule, and a hidden field is not checked for being required, so a form could go out quietly missing an answer. Those references are now a warning while you save a draft and a refusal at publish and at set-ready-for-testing, and the fill screen shows an error on the field rather than computing against nothing. Over the API this is the unknown_field_reference warning code — see conventions and form logic.

A publish that races a save now stops and asks you to retry instead of releasing a version whose recorded details describe a different draft.

Branding uploads no longer accept SVG. Logos and icons take PNG, JPEG, or WebP. Branding images are served from a public address and an SVG file can carry code, so we keep that file type out. Images already uploaded are unaffected.

August 9, 2026

Most of this release is the mobile app, rebuilt around what field users told us after the first round of on-device testing: text that was too small to read, controls that were too small to hit, and a repeating section that could lose a row to a mis-tap. The rest is three additions for people building integrations.

Text in the app is bigger, and it follows the phone's own text-size setting. Two things were wrong. Much of the fill path sat at 13 pixels or smaller — including validation errors, helper text, and the titles of rows inside a repeating section, which is exactly the text a user reads when something has gone wrong. And turning the text size up in the phone's settings was not an answer we could offer, because eight inputs were pinned to a fixed height and the larger text was cut off inside them. Those are fixed, and the app's four smaller text sizes now match what iOS itself uses, so app text lands where a phone user already expects it. A crew does not have one pair of eyes; each person can set their own size and the app will follow it.

A repeating section no longer loses a row to a mis-tap. The collapse and remove controls on a row were about 20 pixels tall and sat next to each other, and removing a row that had nothing in it yet deleted it with no question asked. So the most likely mis-tap — reaching for collapse on a row you had just added — destroyed that row silently. Both controls are now full-size tap targets, and every removal asks first. The wording still tells you which case you are in: an empty row asks plainly, a row with answers in it says what will be lost.

The same section got three other fixes from the trial. There is one Add button now, in a fixed place in the header, instead of one there and a second one at the bottom of an open row. Done scrolls back to the section, rather than leaving you below it with the Add button off screen above you. And an empty section looks like a filled one, so adding the first row no longer rearranges the screen while you are typing into it.

A closed multi-select says what you picked. It read "3 selected", which answers how many rather than which, so checking your own answer meant reopening the picker. It now lists the labels, in the order the form author wrote them, and shortens to two lines with an ellipsis when the list is long. An answer whose option is no longer offered still appears, because it is in the record either way.

Option pickers are the size of what is in them. A five-option picker opened tall enough for four, because the height was a running estimate that had drifted from the real one. The estimate is gone: a short list is exactly as tall as it needs to be, a long one scrolls, and the sheet follows the phone when it is rotated. A small list also stops showing a search box it does not need, which was costing it a row of height. All the sliding sheets in the app — options, lookups, dates, the scanner — now share one shell, so they agree on their corners, their spacing, and how much room they leave at the foot of the screen.

The keyboard gets out of the way. Opening a picker, a lookup, a date, or the scanner now puts the keyboard away first, instead of leaving it covering the control that just opened. The lookup picker no longer jumps straight into its search box, so a short list is visible without typing. And on iPhone, number pads carry a Done button, which they had no way to close before except tapping an empty part of the form.

The forms screen remembers grid or list. Picking list once used to last until you navigated away. It now sticks, along with which collection groups you collapsed and how each list is sorted. Status filters deliberately do not stick: a filter hides records, and coming back days later to a screen quietly missing half your work reads as lost data rather than as your own setting.

Selected answers and buttons no longer look alike. A selected choice and a primary button were the same solid blue with white text, so a Done button read as an option you had picked. Selected choices now carry a lighter blue with dark text, buttons keep the solid fill, and a symbol on a control now means it does something rather than that it is your answer.

A date field can use a spinner instead of a calendar. The calendar grid is one tap for next Tuesday and several for a date years away, because changing the year means opening a second control. A birth date, an expiry, or a historical observation is the opposite case. The form author now chooses per field, in the field's settings panel or through the API with picker_style. Calendar stays the default, so every existing form looks exactly as it does today, and the choice applies to the date only — time has always used a spinner. See the field reference.

Forms written through the API now come back with warnings. POST and PATCH on /forms returned the saved form and nothing else, so an integration authoring a form never heard the things the form builder tells a person in its warnings panel — an icon that will not draw, a repeated field id, a lookup filter pointing at a field that is not there. That is how a form with six unusable page icons reached a device with no signal at any point along the way. The response now carries a warnings list with a code for each one. Warnings never block a write and never will, so nothing that saves today stops saving. The five codes are in conventions.

form.assigned and form.unassigned can be subscribed to through the API. Both events have been documented, delivered, and available in the dashboard for some time, but the API's list of subscribable events was a separate hand-kept copy that never gained them — so asking for one through the API was rejected as an invalid event name. The list is now read from the same place the payload schemas come from, so the two cannot disagree again. Webhooks lists what you can subscribe to.

AI tools can ask about reports. The MCP server gained four read tools on the reports:read scope: the report templates, one template in detail, where a report is set up to be delivered, and what was actually delivered — with the status, the destination, and the error when one failed. That last one answers "did last night's report go out, and where". There is deliberately no tool that runs a merge: a merge sends real email to real people, and that should be a person's decision rather than something an assistant does while answering a question.

This release also includes internal permission, test, and reliability work with no change to how the product behaves.

August 7, 2026

Much of this release enforces rules that were written down but not applied. Nothing in your forms or your data changed. What changed is that settings you already made now take effect, so a form or an integration that was quietly getting away with something will start being stopped. The first four entries below are the ones to read before this reaches your field users; the rest are additions and fixes.

A required lookup or repeating section now stops a submission. Marking a lookup field or a repeating section as required had no effect on a device: the form submitted with nothing selected and no rows added. Both are now checked, and so are required fields inside a repeating section's rows, which nothing checked before. A field user sees the usual "Check these fields" list, and a row problem names its row — for example "Evaluations, row 3: Variety: Please select an option". A collapsed row shows a count of its problems, and the page bar marks the page holding it.

If a form has been collecting incomplete answers because of this, field users will start seeing those submissions blocked. Before this reaches them, look at the fields you marked required on lookups and repeating sections and confirm each one is genuinely required. Records already collected are untouched — they keep whatever they were submitted with. See the mobile app.

Forms written through the API are checked against the field schema. POST and PATCH on /forms accepted any shape at all and stored it, so a malformed field was found only later — on a device, or as a form that would not open. Both now check the whole field tree and return 400 validation_failed with one entry per problem, naming the path.

An integration that has been sending something the schema does not allow will start failing. The three that turn up most: an extra key that was silently ignored before, most often name on a field; a field id that is not lowercase letters, numbers, and underscores; and a missing interface, which is required on every type. Publishing also re-checks the stored draft, so a form written before this release can be refused at publish with the same list of problems — open it in the form builder to fix them. The full shape is now written down in the field reference.

A circular visibility rule no longer takes the whole form down, and cannot be published. When one Visible when rule referred back to itself in a circle, the app gave up on visibility for the entire form: a "Form Configuration Error" notice, and every field meant to be hidden appeared instead — including fields holding ids a field user should not see. Now only the fields in the circle are affected. They are shown and marked with an error, the rest of the form behaves normally, and the notice names them rather than declaring the form broken.

Publishing a form that contains a circular rule is now refused, from the dashboard and from the API, so one cannot reach a device in the first place. Saving a draft that contains one still succeeds — a form mid-edit can pass through a circle on its way to something sensible — but the API answers with a warnings entry so you know before you publish. See form logic and warnings.

A bad id in a request body returns 404 instead of 500. Sending an organization_id, report_id, or any other reference that does not exist produced a 500 with raw database text in the message. It now returns 404 not_found with plain wording — "Organization not found", or "A referenced record does not exist" when the reference cannot be named. Retry logic that treated these as server faults and backed off should treat them as a bad request instead.

Long lookup lists no longer stop at 100 rows. A lookup pointing at a data source with more than 100 rows showed the first 100 and nothing else, with no notice — scrolling ended early, and the rest of the rows could only be reached by typing a search that matched them. The picker now loads another 100 as the user scrolls, with a line at the top reading how many are shown out of how many match, for example "Showing 100 of 512 — search to narrow". Paging reads the device's own copy, so it works with no signal.

One limit remains, and the picker now says so. When a lookup's filters have to be calculated from other answers, the app works from a window of the first 2,000 matching rows. Past that it tells the user to search rather than showing a short list as if it were the whole one.

Four new settings for people building forms. Each is on the field's settings panel in the builder, and each has a key you can set through the API — see the field reference.

  • Default value — the value a new record starts with, which the person filling the form can change. Available on text, number, choice, and date fields. It is applied when a record is created, and never reapplied to a record that already exists, so it cannot overwrite a deliberate blank. Records submitted through the API are stored exactly as sent; there is no fill session to seed, so send the value yourself.
  • Hidden — the field is never shown to a field user but still holds a value. Written for ids and keys that feed lookup filters or your own systems. It replaces the old trick of writing a Visible when rule that always returns false, which was the fragile mechanism protecting exactly those fields. A hidden field skips required checks.
  • Hide when empty — the field appears only once it has a value. Written for calculated fields that should stay out of the way until they compute something. Zero and false count as values.
  • fieldscroll in expressions — formulas, Visible when rules, and per-option visibility can now read what the platform knows about the session, starting with who is filling the form: fieldscroll.user.roles, .email, .id, and .is_owner. Gate on a role rather than an email, so the form survives staff changes. This is a convenience, not a permission boundary: an option hidden this way is hidden in the interface only, and the value can still be submitted through the API.
  • Default formula — a starting value that is worked out rather than fixed, and still only worked out once. Use it to capture something about the moment a record was started, such as who started it. A formula cannot do this: it runs again every time the form is opened, so a formula reading who is filling the form ends up recording whoever opened the record most recently.
  • One starting value per field — a field takes its value from a formula, a default formula, or a default value, and setting two is now refused when the form is saved rather than one quietly winning. The same applies to Hidden together with Hide when empty, which contradicted each other and was previously accepted.

Changing a choice field's input style no longer erases its choices. Switching a dropdown to toggle buttons, or the reverse, replaced every option you had written with a placeholder. Because an option's label is the stored value, this was a data problem rather than an annoyance, and it saved without complaint. Options and their per-option visibility rules now carry across the change. Forms already damaged this way are not repaired — the old labels are gone — so check any choice field whose input style you changed recently.

A database sync that skipped its deletes now says so. When rows disappear upstream, FieldScroll refuses a delete pass that would remove more than half the rows in one go — a guardrail against a bad sync emptying a source. That refusal was recorded as a successful sync. The source showed a healthy status and a fresh sync time while rows deleted in your database stayed selectable in forms and stayed on every device. The data sources list now has a Sync column, and a source in this state shows Needs attention with the reason. When the shrink is genuine, there is no way to wave it through from the dashboard yet — contact support and we will clear it.

More of the release workflow is reachable from the API and from AI tools. POST /forms/{id}/ready-for-testing puts a draft in the test slot, so a scripted setup no longer has to choose between publishing untested and stopping for a manual click. Webhook subscriptions that target a report template can now be created through the API rather than only in the dashboard. And the MCP server gained four form tools — create, update, set ready for testing, and publish — all requiring forms:write. That scope can publish a form to every device assigned to collect with it, so grant it deliberately.

A form promoted through the API grants nobody access, because an API key is not a person. Assign collectors and testers yourself.

The page bar now says how much is left, before you try to submit. On a form split into pages, a field user could only find out where the unfinished required questions were by submitting and being rejected. Each page button now carries a plain count of the required questions on it that are still empty, updated as the form is filled. After a failed submit the count switches to the red count of actual problems, as before. The two are different claims — one says "there is more to do here", the other says "something here is wrong" — so they do not share a color.

A question that sits outside every page shows on every page, and now counts toward every page's number. Before, it counted toward none of them, so a user could clear every badge and still be stopped at submit with nothing on screen explaining why. Calculated and read-only questions are left out of the count, since nobody can fill them. The builder's test-mode preview shows the same counts as a device.

Portrait photos are no longer stored bigger than they should be. Photos are resized on the device to fit inside 1,920 by 1,920 pixels. That fit was being measured against the width only, so a photo taken with the phone held upright — the normal way — was stored about a third larger than intended, and took correspondingly longer to upload on a weak signal. It is now measured against the longer edge, whichever way the phone was held. A photo that already fits is left alone rather than being enlarged to the ceiling, which used to make small images bigger than the originals. Photos already collected are unchanged.

This reduces what a photo costs to move and store. It does not change how photos come out in rendered documents — a report template still embeds the full-size image regardless of the width it displays at.

One way to name an icon. A form icon or a page icon is now either a Material Design name written as mdi:map-marker, or a link to an image you uploaded. Nothing else. There used to be a second, older set of bare names, and twenty-nine names existed in both sets drawing different pictures — so an author could pick an icon in the builder, publish, and have the crew see something else. The builder's picker is unchanged to look at, and the curated set gained camera, image, truck, and twelve farming icons it was missing.

If you build forms through the API, this is the part to check: a bare name like clipboard is not a valid icon value, and never was. The builder now flags any icon value that will not draw and suggests the closest real names, so the mistake is caught while you are authoring rather than showing up as a blank-looking button in the field. It stays a warning and never blocks a save, because an icon is cosmetic and a form that saved yesterday has to keep saving today. The field reference now states the rule and lists the near-misses people actually write.

A lookup filter can read one detail of another lookup answer. When a lookup field's list has to narrow based on something already picked — varieties of the species chosen at the top of the form — the filter can now point straight at species.id. Until now it could only name a plain field, so forms grew hidden fields whose only job was to copy an id from one place to another. Those fields were never free: each one stored a value in every record and appeared in exports and webhook payloads. They can be removed, and new forms do not need them.

The same section of the docs now covers scope, which decides whether a filter inside a repeating section reads the row it is in, the section above it, or the top of the form. It always worked and was written down nowhere, which is why people built the copying fields by hand. Both are walked through in form patterns.

Reading data-source rows through the API dropped rows, and no longer does. Walking GET /organizations/{org}/data-sources/{id}/rows page by page could return only part of a source, with no error and a next-page marker that looked normal. It affected any source whose rows carry a display order, which every database-synced source does. On a 25-row test source, a full walk returned 12 rows. If you have audited a data source this way, the result was not trustworthy — run it again.

Three filters were added to the same endpoint, so an audit no longer means reading everything: external_id fetches the one row you are asking about, updated_after returns only rows written since a timestamp, and include_deleted includes removed rows so a deletion reads as a change rather than an absence. All three are also on the matching AI tool. A new page, checking a data source is current, covers which sync fields prove what — including why data_version staying at 1 on a database-synced source is correct and not a stalled sync.

AI tools get a smaller page instead of an error. A list request through the MCP server whose results were too large to return failed outright, and the caller had to guess a smaller page size and try again — a wasted call that still counted against the rate limit. The server now returns the largest page that fits. A page shorter than you asked for is normal; the next-page marker, not the row count, still says whether the list has ended.

This release also includes internal reliability and test work with no change to how the product behaves.

August 4, 2026

CSV import for data sources was rebuilt. The importer on a data source's rows page now reads standard spreadsheet files correctly, including values that contain commas, quotes, or line breaks. Before this release those values split across columns and shifted every column after them, with no error shown. If you imported a file containing such values, check the affected rows — the corrupted data is still there.

Re-importing will not repair those rows. Rows added by the old importer carry no identifier, so the new import cannot match them: it adds corrected copies alongside the bad ones, and Remove rows not in the file will not clear them either. Delete or correct the affected rows by hand first, then import the file again.

The import also gained the pieces it was missing:

  • You pick an identifier column, so re-importing a corrected file updates the matching rows instead of adding duplicates.
  • A Remove rows not in the file option handles the case where the file is the complete list. It is off by default, and a safety limit stops an import that would delete more than half the rows.
  • Files with a bad row are rejected with the line number, instead of importing part of the file.
  • One import handles up to 5,000 rows. Larger loads go through the API.

Details are in data sources.

Fields that depend on a calculated value now show and hide correctly. If a form looked something up or calculated a value, and other fields used Visible when to branch on that value, those fields stayed in whatever state they had when the form opened — usually hidden. On long forms this could leave whole pages blank. Both the mobile app and the builder's preview are fixed. No change is needed to affected forms; open them again and the logic runs. See form logic.

A related fix clears validation errors that a hidden calculated field could raise, which showed as an error marker on a page with no visible problem.

Per-key API rate limits are enforced as documented. A key with its own per-minute limit set at minting time now gets that limit. Until this release every key was held to the instance-wide default no matter what was set on it, and requests arriving at the same moment could slip past the count. Two things follow. A key with a limit above the default now has the headroom it was given. A key with a limit below the default is now held to it, so an integration running against such a key may start seeing 429 rate_limited — see rate limiting for how to back off, or ask for a higher limit on the key.

Only org admins can hand out the org admin role. Inviting someone as an org admin now requires being an org admin or the instance owner, on both the new-user and existing-user paths. This matches what user management has always described.

This release also includes internal security, performance, and reliability work with no change to how the product behaves.