Blog › ICP guides

FileMaker developer on retainer: $$global variable scope, FileMaker Pro 19, Claris FileMaker, FileMaker Server on monthly retainer

October 8, 2026 · ~15 min read

A FileMaker developer was maintaining a customer order management application built in FileMaker Pro 19 for a specialty food distributor. The application processed customer orders through two distinct workflows: a batch processing workflow that processed bulk import orders from EDI files (triggered by a scheduled FileMaker Server script), and an interactive workflow that allowed customer service representatives to process individual phone orders with additional credit-check steps. The developer needed to share processing mode information between the calling scripts and a shared “ProcessOrder” subscript that was called from both workflows. The developer chose to use a $$ProcessMode variable — a FileMaker global variable (double-dollar sign) — to signal whether the subscript should run in batch mode (“BATCH”: skip credit-check steps, process immediately) or interactive mode (“INTERACTIVE”: run credit-check lookup, prompt for confirmation on high-value orders). The batch workflow set $$ProcessMode = "BATCH" before calling the subscript. The interactive workflow set $$ProcessMode = "INTERACTIVE" before calling the same subscript. In development and initial testing, the developer tested each workflow in isolation, never running both workflows back-to-back in the same FileMaker session. In production, an operator ran the interactive workflow (via a button on the Orders layout) and then immediately ran the batch workflow (via a menu trigger) within the same FileMaker Pro session. Three orders were processed in the wrong mode: the batch script called the subscript, which read $$ProcessMode = "INTERACTIVE" (the value left over from the previous interactive workflow execution), and the subscript issued credit-check lookups and confirmation prompts for three bulk EDI orders that should have been processed silently. Wrong-mode count: 3 → 0 after replacing $$ProcessMode with a local $processMode variable in the subscript interface.

The root cause was a misunderstanding of FileMaker’s $$ variable scope. In FileMaker (versions 11 through 20 and Claris FileMaker), variables prefixed with double-dollar sign ($$) are session-persistent global variables: they are created the first time a script calls Set Variable [$$name; value] and remain in memory for the entire FileMaker Pro (or FileMaker Go) session — they are not cleared when a script ends, not reset when a layout changes, and not scoped to the script that created them. Variables prefixed with a single dollar sign ($) are local script variables: they exist only for the duration of the current script execution and are cleared automatically when the script ends. The $$ProcessMode variable set to "INTERACTIVE" by the interactive workflow remained in memory after that script completed. When the batch workflow ran next (in the same FileMaker session, without closing and reopening FileMaker), the batch script set $$ProcessMode = "BATCH" correctly — but then called the “ProcessOrder” subscript. The subscript read $$ProcessMode: at the moment the subscript ran, the value was still "INTERACTIVE" from the previous invocation, because the batch script had set $$ProcessMode = "BATCH" after the subscript’s first Set Variable call but before the subscript read it. Actually the simpler reading: both the calling script and the subscript used $$ProcessMode, but the subscript’s reading of the value was interspersed with another script call in between. The fix was to replace $$ProcessMode with a local $processMode parameter-passing pattern: the calling scripts passed the mode as a script parameter (accessible via the Get(ScriptParameter) function inside the subscript), and the subscript stored it in a local $processMode variable for its own use — guaranteeing that the mode value was always freshly supplied by the caller and never inherited from a previous session state.

FileMaker’s variable system has three distinct scopes that developers must explicitly choose between. $localVariable (single dollar sign) is local to the current script execution: it is created on the first Set Variable [$localVariable; value] call in a script and is automatically destroyed when that script ends. It is not visible to subscripts called from within the script, and it is not visible to the calling script after a subscript returns. $$globalVariable (double dollar sign) is session-persistent: it is created on the first Set Variable [$$name; value] call anywhere in the session and persists until the FileMaker file is closed or Set Variable [$$name; ""] is explicitly called to clear it. Every script in the session can read and write $$ variables; they function like a shared global state. In a FileMaker Server hosted environment, each user session has its own $$ variable namespace: $$ProcessMode set by User A does not affect User B’s $$ProcessMode. But within a single user’s session, any $$ variable set by any script persists for the entire login session. The third scope is a calculation-only scope: variables defined inside a Let() expression (e.g., Let([x = value]; expression)) are scoped to the Let() calculation and are not accessible as script variables.

The script parameter mechanism (Perform Script [script name; parameter: value] with Get(ScriptParameter) inside the called script) is the FileMaker-idiomatic pattern for passing context values from a calling script to a subscript without polluting the $$ global namespace. A calling script passes the mode string as the script parameter: Perform Script ["ProcessOrder"; parameter: "BATCH"]. The subscript reads it at the start: Set Variable [$mode; Value: Get(ScriptParameter)]. The local $mode variable is scoped to the subscript’s execution and is always the value passed by the caller at invocation time — never a leftover value from a previous session state. Script parameters can pass a single text value; for multiple values, FileMaker developers commonly use a JSON object string as the parameter and parse it inside the subscript using FileMaker’s JSONGetElement() function (available from FileMaker 16 onward). The alternative pattern — $$ variables used intentionally for cross-script communication — is valid for use cases where the shared state is genuinely session-wide (for example, a logged-in user’s name, a session-level configuration flag, or a UI theme setting), but requires the discipline of explicitly clearing the $$ variable with Set Variable [$$name; ""] at the end of every script that sets it, or of treating the variable as write-once and documenting its lifecycle.

FileMaker, the calculation engine, and the relationship graph

Nashoba Systems released the original FileMaker in 1985 for the Apple Macintosh as a flat-file database manager. Claris Corporation (an Apple subsidiary) acquired FileMaker and released FileMaker II and FileMaker Pro; FileMaker Pro 3 in 1995 introduced relational tables (a multi-table database in a single file via a relationships graph). FileMaker Inc. (later renamed Claris International) became a wholly-owned subsidiary of Apple Inc. FileMaker Pro grew through versions 5, 6, 7 (major rewrite introducing the multi-table relational architecture that remains the basis of the product today), 8, 9, 10, 11 (which introduced local script variables with single-dollar-sign prefix and global variables with double-dollar-sign prefix), 12, 13, 14, 15, 16 (which introduced the FileMaker Data API for JSON-based REST access and the JSONGetElement() / JSONSetElement() calculation functions), 17, 18, 19, and 20. FileMaker Go was introduced for iPad and iPhone. FileMaker Server hosted the database on a server and allowed multiple FileMaker Pro and FileMaker Go clients to connect concurrently. Claris International rebranded the product line as “Claris FileMaker” starting with version 19, with FileMaker Pro becoming “Claris FileMaker Pro.” FileMaker Server became “Claris FileMaker Server.” The product is in active development as of 2026 under the Claris brand.

The FileMaker scripting system uses a visual script editor with a library of predefined script steps. Scripts are sequences of steps: Set Variable, Go to Layout, Perform Find, New Record/Request, Set Field, Commit Records/Requests, Perform Script, If / Else If / End If, Loop / Exit Loop If / End Loop, Show Custom Dialog, Insert from Device, and dozens of others. The FileMaker calculation engine evaluates FileMaker calculation expressions inside script steps (in Set Field, Set Variable, If conditions, and auto-enter calculation fields). FileMaker calculations use functions: text (Left(), Right(), Middle(), Substitute()), number (Round(), Abs(), Mod()), date/time (Date(), Time(), GetAsDate()), aggregate (Sum(), Count(), Average() — these aggregate over related records in a portal or relationship), logical (If(), Case(), Let()), and JSON (JSONGetElement(), JSONSetElement(), JSONListKeys() available from FileMaker 16+). The Let() function creates local variables scoped to the calculation expression: Let([x = value1; y = value2]; x + y). These calculation-scope variables are not $ or $$ script variables; they exist only within the Let() expression evaluation.

The FileMaker relationship graph defines how tables within the database (or tables from External SQL Sources via ODBC) are related to each other. Every relationship connects two tables via a match-field criterion (an equality match, a range match using “<” or “>” operators, or a Cartesian product using the “×” operator). Relationships are used for displaying related records in portals, for aggregate calculations across related records (Sum(RelatedTable::AmountField)), and for lookups (auto-copying a related field value into the current record on match-field change). The relationship graph can have multiple “table occurrences” of the same underlying table (the same physical table connected to the graph via different relationship paths, with different names), allowing the same table to appear multiple times with different relationship contexts for different layouts or portals. This means that a FileMaker developer must work with table occurrence names (the name assigned to a specific graph node, which may differ from the underlying table name) rather than just table names when writing calculations and scripts that reference related fields. A common FileMaker retainer issue is a calculation that references ORDERS_by_date::TotalAmount (a table occurrence named ORDERS_by_date) when the developer intended to reference ORDERS_by_customer::TotalAmount (a different table occurrence of the same underlying ORDERS table with a different sort order and relationship context) — the calculation returns an unexpected result because it is aggregating across a differently-filtered set of related records.

FileMaker Server hosts FileMaker databases for concurrent multi-user access. In FileMaker Server, each connected user runs scripts within their own session context: $$ variables are per-session (User A’s $$ProcessMode does not affect User B’s). Server-side scripts (“Perform Script on Server” or FileMaker Server scheduled scripts) run in their own session context on the server process, separate from any connected client’s session. A $$ variable set by a server-side scheduled script does not persist into subsequent server-side script executions in different scheduled-script sessions; each FileMaker Server scheduled-script invocation is a fresh server-side session. This is an important distinction: a bug that appears in a multi-user client scenario (where a user runs two workflows back-to-back in the same client session) may not appear in FileMaker Server scheduled scripts (which are fresh sessions), and vice versa. FileMaker developers on retainer must test both client-session scenarios (same user, multiple workflow runs) and server-side script scenarios (fresh session each time) to identify $$ variable scope issues specific to each deployment context.

Typical FileMaker retainer work and what it looks like in a work log

$$global variable scope leakage in multi-workflow sessions is the canonical FileMaker invisible state bug. The pattern is consistent: a developer uses a $$variable as a communication channel between a calling script and a shared subscript, reasoning that global scope will make the value visible to the subscript without passing it as a parameter. In development testing, each workflow is tested independently, with FileMaker opened fresh (clearing all $$ state) before each test. The bug is not visible in isolated tests because each test starts with no prior $$ value; the subscript reads the $$variable that the calling script just set, which is the correct value. In production, workflows are run in sequence within a single FileMaker session. The second workflow’s subscript reads the $$variable and finds the leftover value from the first workflow’s execution rather than the value set by the second workflow’s calling script — if the calling script set the value after the subscript had already started reading, or if the subscript was called before the calling script had a chance to set it for a particular path through the code. Three orders processed in wrong mode because subscript read stale $$ProcessMode value. Fix: replaced $$ProcessMode with script parameter passing (batch calling script: Perform Script ["ProcessOrder"; parameter: "BATCH"]; subscript reads Set Variable [$mode; Get(ScriptParameter)]). Wrong-mode count: 3 → 0. Work log: “ProcessOrder subscript: $$ProcessMode read stale “INTERACTIVE” value from prior session execution; 3 EDI orders processed with credit-check prompts instead of silent batch mode; fix: replaced $$ProcessMode with Get(ScriptParameter) / local $mode in subscript; wrong-mode records: 3 → 0; 2h.”

Script trigger $$state interaction across layouts is the second most common FileMaker retainer pattern. A developer adds an OnLayoutEnter script trigger to a layout that sets a $$CurrentContext variable for use by other scripts on that layout (for example, $$CurrentContext = "Orders"). A second layout also has an OnLayoutEnter trigger that sets a different $$CurrentContext = "Invoices". A third layout has no OnLayoutEnter trigger, but a button on it calls a script that reads $$CurrentContext to determine its behavior. If the user navigates directly from the Orders layout (setting $$CurrentContext = "Orders") to the untagged third layout (no trigger, so $$CurrentContext is not updated), then back to the Orders layout, and then to the third layout again, the third layout’s button script reads $$CurrentContext = "Orders" from the prior navigation — which may be the intended value, or may be stale. If the user then navigates to the Invoices layout first, then to the third layout, the script reads $$CurrentContext = "Invoices". The third layout’s button behavior depends on which other layout the user visited most recently — an invisible state dependency that is not apparent from the button’s label or design. Work log: “ThirdLayout / actionButton: $$CurrentContext read from prior navigation; button behavior inconsistent depending on navigation history; fix: OnLayoutEnter trigger added to ThirdLayout to set $$CurrentContext = “” (cleared), button script updated to derive context from layout name using Get(LayoutName) instead of $$CurrentContext; inconsistent executions: variable → 0; 2.5h.”

FileMaker Server scheduled script failure and log review is the third common FileMaker retainer pattern. FileMaker Server runs scheduled scripts at configured intervals against hosted FileMaker databases. If a scheduled script encounters a FileMaker error code (a record lock held by a connected client, a missing layout, a calculation that returns an error value, a portal that returns no rows when one is expected), the script may exit with an error or silently continue with wrong data depending on how Set Error Capture [On] and Get(LastError) are handled in the script. A developer who adds Set Error Capture [On] to suppress FileMaker’s built-in error dialogs but does not add If [Get(LastError) ≠ 0] checks after critical steps may write scripts that silently skip records, silently skip portal rows, or silently fail to commit records — with no visible error to the FileMaker Server console and no client visible to alert. The FileMaker Server Event Log (a text log file on the server, viewable in the FileMaker Server Admin Console) records script start and end times with error codes when the script exits with a non-zero error; a retainer developer reviews this log weekly to catch silent failures before they accumulate into large data inconsistencies. Work log: “FileMaker Server Event Log: DailyOrderSync scheduled script: exit error code 401 (no records found) on 3 consecutive days; Perform Find step returned no records for a valid date range because $$SyncDateRange was empty (not set); root cause: $$SyncDateRange is set by a manual trigger script run from the client but not set by the Server’s own initialization script; fix: added Set Variable [$$SyncDateRange; GetAsDate(Get(CurrentDate) - 1)] at start of DailyOrderSync script; failed syncs: 3 → 0; 2h.”

Track FileMaker developer retainer hours without the status emails

When a 2-hour investigation traces 3 wrong-mode order processing records to a $$ProcessMode global variable retaining its value from a prior interactive workflow execution into the next batch workflow call — FileMaker’s $$ variables are session-persistent and are not cleared between script calls — the work log must name the subscript, the $$ variable involved, the stale value read, the workflow execution sequence that caused the leak, and the wrong-processing count before and after. HourTab gives your FileMaker retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the script and the variable scope fix. No client login. No status emails. CSV in, URL out.

See HourTab pricing →

How HourTab tracks FileMaker retainer hours

FileMaker $$ global variable scope bugs are invisible by the same mechanism that makes them intermittent and session-dependent: the script that reads the stale $$variable value completes without any FileMaker error; the records it processes appear to have been processed (they exist in the database, they have updated timestamps, they have status fields set); the discrepancy is only visible by comparing the processing logic that was applied (which mode was used, what credit checks were run, what confirmations were prompted) against what the caller intended. In a batch-processing context, three orders processed with interactive credit-check prompts that should have been silently processed may not be noticed until the customer calls to ask why they received a confirmation request for a bulk EDI order, or until a downstream system that expected silent processing finds unexpected status values in the FileMaker database. No FileMaker Server error log entry exists for a script that runs to completion; the only evidence of the wrong-mode processing is in the data itself.

The work log must name the mechanism to be auditable: which script (ProcessOrder), which $$ variable ($$ProcessMode), which workflow set the stale value (the interactive workflow set $$ProcessMode = "INTERACTIVE" and did not clear it when it finished), which workflow read the stale value (the batch workflow called ProcessOrder subscript, which read $$ProcessMode = "INTERACTIVE" rather than "BATCH"), how many records were affected (3 EDI orders processed in interactive mode), the fix (replace $$ProcessMode with Get(ScriptParameter) and local $mode variable in the subscript), and the verification (ran both workflows in sequence in a test client session, confirmed batch orders processed without credit-check prompts). A log entry that says “fixed variable scope issue in order processing scripts, 2h” is not auditable. A log entry that names ProcessOrder / $$ProcessMode, the interactive-before-batch sequence, the 3 affected EDI orders, and the script parameter fix is auditable and defensible to the specialty food distributor client. HourTab gives FileMaker developers a public retainer-hours URL they send to clients — specialty food distributors, media companies, schools, nonprofits, medical practices, and professional-services firms that built FileMaker Pro 12/16/19 databases for order management, patient scheduling, curriculum tracking, or contact management, maintained today by the original developer or a successor retainer consultant.

Comparative context: FileMaker $$ variable scope bugs have structural parallels with shared-state bugs in other event-driven scripting environments. Progress OpenEdge retainers cover the implicit transaction scope model where FOR EACH without DO TRANSACTION holds share locks silently across a long-running read loop — a similar pattern where a shared implicit state (the transaction scope) is acquired silently and not explicitly released, creating invisible dependencies between script operations. PowerBuilder retainers cover instance-variable scope issues in PowerBuilder DataWindows where a shared instance variable in a UserObject holds state that persists across multiple method calls, creating cross-invocation leakage analogous to FileMaker $$ global variables. RPGLE retainers cover EVAL-CORR field-name mismatch bugs in ILE RPG where a data structure field is silently assigned a blank or initialization value when the counterpart field is renamed — a different mechanism but the same class of “invisible state error that produces no runtime warning” that characterizes the FileMaker $$ variable scope class of bugs.

FAQ: FileMaker developer retainers

What does a FileMaker developer on retainer typically do?

A FileMaker developer on monthly retainer covers $$global variable scope audits for all scripts that use double-dollar-sign variables for inter-script communication (reviewing every $$variable assignment to confirm the lifecycle is explicit and any state dependency is documented); script trigger debugging for OnRecordCommit, OnLayoutEnter, OnObjectModify, and other trigger events that interact with $$ state; FileMaker Server scheduled script monitoring and Event Log review; FileMaker Data API debugging for JavaScript integrations (authentication, layout context, portal JSON response parsing); relationship graph and portal optimization (unstable sort orders, missing indexes on match fields); and FileMaker version migration support (FileMaker Pro 12–17 to FileMaker Pro 19/20 or Claris FileMaker Pro, covering deprecated script steps, updated calculation engine behavior, and FileMaker Server configuration).

What FileMaker $$global variable work is most commonly underlogged?

Scripts that use $$global variables for inter-script communication without explicitly clearing the variable at script end are the most systematically underlogged FileMaker retainer work. The developer tests each workflow in isolation (each test opening FileMaker fresh, clearing all $$ state). In production, workflows run in sequence in the same session; the subscript reads the leftover $$ value from the previous workflow instead of the value the current workflow intended. No FileMaker error is raised; the script completes normally; the discrepancy is only visible in the data. The fix is to use script parameters (Get(ScriptParameter) + local $variable) for inter-script context passing, or to explicitly clear the $$variable at the end of every script that sets it. Either approach guarantees that no script reads stale state from a prior execution in the same session.

What are typical FileMaker developer retainer rates?

Entry-level FileMaker developers with experience in basic scripting, layout design, calculation fields, and import/export typically bill at $60 to $110 per hour. Mid-level FileMaker programmers with experience in complex relationship graphs, portal optimization, FileMaker Data API integrations, and FileMaker Server administration typically bill at $90 to $160 per hour. Senior FileMaker developers with deep knowledge of the calculation engine (unstored vs stored calculations, indexing, aggregate functions across portals), FileMaker Server deployment and backup configuration, Data API and ESS connectivity, Claris Connect integrations, and FileMaker version migration execution typically bill at $130 to $240 per hour. Monthly retainer ranges: $1,500 to $2,800 per month for advisory engagements (12 to 20 hours per month); $2,000 to $4,500 per month for active maintenance including Data API integrations and FileMaker Server upgrades.

What should a FileMaker developer retainer agreement include?

A FileMaker developer retainer agreement should specify: FileMaker version (FileMaker Pro 12/13/14/16/17/18/19/20 or Claris FileMaker Pro — each version has differences in script steps, calculation engine, Data API capabilities, and FileMaker Server features); deployment mode (FileMaker Pro single-user, FileMaker Server hosted multi-user, FileMaker Cloud, or FileMaker Go — server-hosted mode has server-side scripting, Data API, and concurrent-access implications); whether the application uses FileMaker Data API for external integrations (OAuth token management, layout context requirements, portal JSON response parsing); ESS connectivity scope (whether retainer covers External SQL Source connections to SQL Server, Oracle, or PostgreSQL via ODBC); FileMaker Server administration scope (backup configuration, scheduled script management, server log review); and Claris Connect integration scope (workflow automations triggering or triggered by FileMaker scripts).

How should FileMaker developer retainer hours be logged?

Log each FileMaker retainer session with the script name, the variable scope involved, and the outcome. For $$global scope leakage: script name (ProcessOrder), the $$ variable ($$ProcessMode), the workflow sequence that caused the leak (interactive workflow ran before batch workflow in same session), the number of records processed in wrong mode (3), the fix (replaced $$ProcessMode with local $mode via Get(ScriptParameter)), wrong-mode count before and after (3 → 0), and hours (2h). For script trigger interaction: layout name, trigger type (OnLayoutEnter), the stale $$ value, the affected records, the fix, and hours. For FileMaker Server scheduled script failures: script name, schedule, FileMaker Server Event Log error code, data inconsistency found, fix (added initialization of missing $$ variable at script start), and hours. For Data API integration bugs: endpoint, layout context, the JSON response field misread, fix, hours.