1. Getting Started

Structa is a native macOS database application builder. It lets you create relational databases, design visual forms, write automation scripts, and publish your data to the web — all from a single application running on your Mac. Think of it as a modern, native alternative to FileMaker Pro, built with Swift and SQLite.

1.1 Projects & Workspace

Everything in Structa revolves around a project. A project is a self-contained database that holds your tables, fields, relationships, forms, scripts, and data. You can have multiple projects open at once — each opens in its own window.

To create a new project, use File > New Project... (Cmd+N). Choose a location and name, then click Save. Structa creates a .structa package and opens it in Design mode.

To open an existing project:

The workspace consists of a sidebar (left) for navigation and a detail area (right) that shows the active editor. Sidebar sections include Project, Design (Tables, Relationships, Forms, Scripts), Data (Browse Data), Deploy (Publish, Host Mode), and Configuration (Settings).

1.2 The .structa Format

A .structa file is a macOS package — a directory that Finder displays as a single file. Inside the package:

MyProject.structa/
    manifest.json          ← Project metadata (name, version, dates)
    project.sqlite         ← SQLite database (schema + data)
    assets/                ← File attachments and images
    templates/             ← Exported template bundles
    snapshots/             ← Autosave and recovery snapshots
    previews/              ← Form preview image cache
    exports/               ← CSV and other export output
    logs/                  ← Diagnostic logs

The manifest.json records the format version, application version, project name, creation and modification timestamps, default publish profile, and startup mode. The project.sqlite file contains both Structa's internal metadata tables and all user-created data tables.

Note: All file writes use atomic operations — data is written to a temporary file first, then renamed into place. This prevents data loss from crashes or power failures during a save.

1.3 Creating Your First Project

  1. Launch Structa. The Welcome screen appears.
  2. Click "New Project" (or press Cmd+N). Choose a location and name, then click Save.
  3. Navigate to Tables in the sidebar.
  4. Click "Add Table" and enter a display name (e.g., "Contacts").
  5. Add fields by clicking the "+" button: First Name (Text), Last Name (Text), Email (Email), Phone (Text).
  6. Save with Cmd+S.
  7. Switch to Data Browser (Cmd+Shift+R) and start adding records.
Tip: To start from a pre-built database, choose a bundled template (CRM Lite, Inventory, Invoicing Lite, Job Tracking, or Notes & Collections) from the onboarding wizard.

1.4 The Interface

SectionItemsPurpose
ProjectWelcomeLanding screen with quick actions and recent projects.
DesignTables, Relationships, Forms, ScriptsDefine schema, layouts, and automation.
DataBrowse DataView, search, create, edit, and delete records.
DeployPublish, Host ModePublish to the web or run as a network service.
ConfigurationSettingsApplication and project preferences.

1.5 Application Modes

Structa operates in one of three modes, switchable from the Runtime menu or View > Mode:

ModePurpose
DesignCreate and modify tables, forms, scripts, and relationships.
RuntimeUse the database day-to-day: browse data, enter records, run scripts.
HostStart the embedded web server and serve your database over the network.

2. Schema Designer

The Schema Designer is where you define the structure of your database. Navigate to Tables in the sidebar to access it. Here you create tables, add fields, set validation rules, and manage schema changes.

2.1 Creating Tables

Click the "+" button at the bottom of the table list. Enter a Display Name (e.g., "Invoice") and an optional Plural Display Name (e.g., "Invoices"). Structa generates a sanitized internal name automatically — lowercased, spaces become underscores, non-alphanumeric characters are stripped, and names starting with a digit are prefixed with t_.

Every table automatically receives a _rowid INTEGER PRIMARY KEY AUTOINCREMENT column as its first column, providing a stable unique identifier for every record.

2.2 Field Types Reference

Structa supports 18 field types. Add a field by clicking the "+" button in the field list and selecting a type:

TypeSQLite StorageDescription
textTEXTSingle-line string, up to 255 characters by default.
longTextTEXTMulti-line string with no practical length limit. Renders as a text area.
integerINTEGERWhole number (64-bit signed).
decimalREALFloating-point number (64-bit double).
booleanINTEGER (0/1)True or false. Renders as a checkbox.
dateTEXT (ISO 8601)Calendar date without time (e.g., 2026-04-13).
timeTEXT (HH:MM:SS)Time of day without date.
dateTimeTEXT (ISO 8601)Combined date and time with timezone.
uuidTEXTAuto-generated UUID v4 on record creation.
autoIncrementPrimaryKeyINTEGERAuto-incrementing integer. Typically used as a user-visible primary key separate from _rowid.
foreignKeyINTEGERReferences another table's primary key. Used to create relationships.
calculationVariesComputed dynamically from a formula. Not stored on disk — evaluated at read time.
jsonTEXTArbitrary JSON data. Validated on save to ensure well-formed JSON.
imageReferenceTEXTPath to an image file in the project's assets/ directory. Path-only; the bytes live wherever the path resolves to.
containerTEXT (JSON descriptor)First-class file attachment. The user drops a file (PDF, photo, audio, signed contract, anything) onto the field; Structa copies it into <bundle>/Assets/containers/<table>/<field>/<rowid>/<filename> and stores a JSON descriptor with the path, original name, MIME type, size, and upload date. Web clients upload via multipart/form-data and download via the /assets/… route.
emailTEXTEmail address with basic format validation.
urlTEXTURL string with protocol validation.
choiceTEXTSingle value from a list of options. The list can be a static set typed into field properties, or sourced dynamically from another table's column (set in the field inspector via the "Static list / From table" toggle, then pick the source table + field). The runtime dropdown queries the source on demand and falls back to the static list if no source is configured.

2.3 Validation Rules

Each field can have one or more validation rules that are checked when a record is saved:

RuleApplies ToDescription
RequiredAll typesField must have a non-empty value.
UniqueAll typesNo two records may share the same value in this field.
Min Lengthtext, longText, email, urlMinimum character count.
Max Lengthtext, longText, email, urlMaximum character count.
Min Valueinteger, decimalMinimum numeric value.
Max Valueinteger, decimalMaximum numeric value.
Pattern (Regex)text, email, urlRegular expression the value must match.
In ListAll typesValue must be one of an explicitly enumerated set (also used to back choice-field dropdowns).
Not In ListAll typesValue must NOT be in the given set (reserved-word lists, banned strings, etc.).
Custom FormulaAll typesA formula returning a boolean. Reference other fields in the same record; failure shows the formula's name to the user.
Email Formattext, emailStrict email pattern (more rigorous than the cosmetic check on the email type).
URL Formattext, urlMust parse as a syntactically valid URL with a scheme.
Date Rangedate, time, dateTimeValue must fall between an inclusive lower and upper bound (either side optional).
File ExtensioncontainerUploaded file must have one of the allowed extensions (e.g. pdf,jpg,png). Use to gate attachment uploads on the web.

Validation errors appear inline in the Data Browser and Form Designer. If a record fails validation, it cannot be committed.

2.4 Schema Migrations

When you modify a table's structure (add, rename, or remove a field; change a field type), Structa performs an automatic schema migration. The process:

  1. A recovery snapshot is created before any changes are made.
  2. Structa uses SQLite's ALTER TABLE when possible (adding columns, renaming columns).
  3. For destructive changes (removing columns, changing types), Structa creates a new table, copies data with type coercion, drops the old table, and renames the new one.
  4. Foreign key relationships and indexes are rebuilt as needed.
Warning: Deleting a field permanently removes all data in that column. Always save your project and verify the snapshot before making destructive schema changes.

3. Relationship Designer

The Relationship Designer provides a visual canvas for defining how your tables relate to one another. Navigate to Relationships in the sidebar to open it.

3.1 The Canvas

Each table appears as a node on the canvas showing its name and fields. Relationships are drawn as lines connecting a foreign key field to its target table's primary key. You can pan the canvas by dragging the background and zoom with the scroll wheel or trackpad pinch gesture.

+-----------+ +-----------+ | Company | | Contact | +-----------+ +-----------+ | _rowid PK |<-------->| companyID | (FK) | name | 1:N | firstName | | address | | lastName | +-----------+ | email | +-----------+

3.2 Cardinality Types

CardinalitySymbolDescription
One-to-One1:1Each record in Table A matches exactly one record in Table B. Enforced with a UNIQUE constraint on the foreign key.
One-to-Many1:NOne record in Table A can relate to many records in Table B. The most common type.
Many-to-OneN:1Many records in Table A reference a single record in Table B. Same physical shape as 1:N viewed from the other side — pick whichever direction reads more naturally for your schema.
Many-to-ManyN:MRecords on both sides can relate to multiple records on the other. Requires a join table with two foreign keys.

3.3 Creating Relationships

  1. Ensure at least one table has a foreignKey field pointing to another table.
  2. In the Relationship Designer, click "Add Relationship".
  3. Select the source table and its foreign key field.
  4. Select the target table (the table being referenced).
  5. Choose the cardinality (One-to-One, One-to-Many, or Many-to-Many).
  6. Optionally enable Cascade Delete (deleting a parent automatically deletes related children).
  7. Click Save. The relationship appears as a line on the canvas.

3.4 Referential Integrity

Structa enforces foreign key constraints at the application level. When you attempt to delete a record that has related child records, Structa will:

Orphaned records (foreign key values pointing to non-existent parents) are flagged in the Data Browser with a warning indicator.

4. Calculation Engine

The Calculation Engine powers computed fields, script conditions, search criteria, and validation formulas. Formulas are written in a simple expression language that supports field references, operators, function calls, and literal values.

4.1 Formula Syntax

Formulas follow this general pattern:

functionName(argument1, argument2) & " literal text " & fieldReference

Key rules:

4.2 Operators

CategoryOperatorsExample
Arithmetic+ - * / %price * 1.08
Comparison= != < > <= >=age >= 18
LogicalAND OR NOTstatus = "active" AND age >= 18
String concatenation&firstName & " " & lastName

Operator precedence (highest to lowest): NOT, * / %, + -, comparison, AND, OR. Use parentheses to override.

4.3 Field References

Reference fields from the current table by their internal name: firstName. Reference fields from related tables using double-colon notation: TableName::fieldName. Related references follow the relationship graph — the tables must be connected through a defined relationship.

4.4 Formula Examples

// Full name
firstName & " " & lastName

// Line total with tax
quantity * unitPrice * (1 + taxRate / 100)

// Age from birth date
year(today()) - year(birthDate)

// Status label
if(isempty(completedDate), "In Progress", "Completed")

// Conditional discount
case(
    totalAmount >= 1000, totalAmount * 0.9,
    totalAmount >= 500,  totalAmount * 0.95,
    totalAmount
)

5. Formula Function Reference

Structa provides 118 built-in functions organized into 13 categories (string, math, conditional, null, date, conversion, logical, crypto, statistics, advanced math & calculus, system / Get(), arrays, and dynamic / metadata functions). All function names are case-insensitive.

5.1 String Functions (17)

FunctionSyntaxReturnsDescription & Example
length length(text) Integer Number of characters. length("hello")5
lower lower(text) Text Converts to lowercase. lower("Hello")"hello"
upper upper(text) Text Converts to uppercase. upper("Hello")"HELLO"
trim trim(text) Text Removes leading and trailing whitespace. trim(" hi ")"hi"
contains contains(text, search) Boolean True if text contains search. contains("hello world", "world")true
startswith startswith(text, prefix) Boolean True if text begins with prefix. startswith("hello", "he")true
endswith endswith(text, suffix) Boolean True if text ends with suffix. endswith("hello", "lo")true
left left(text, count) Text First count characters. left("hello", 3)"hel"
right right(text, count) Text Last count characters. right("hello", 3)"llo"
middle middle(text, start, count) Text Extracts count characters from start (1-based). middle("hello", 2, 3)"ell"
substitute substitute(text, search, replace) Text Replaces all occurrences of search with replace. substitute("hello", "l", "r")"herro"
position position(text, search) Integer 1-based index of first occurrence, or 0 if not found. position("hello", "ll")3
patterncount patterncount(text, search) Integer Number of non-overlapping occurrences. patterncount("banana", "an")2
substring substring(text, start, end) Text Characters from start to end (1-based, inclusive). substring("hello", 2, 4)"ell"
format format(template, arg1, arg2, ...) Text printf-style template formatting (%s, %d, %.2f). format("$%.2f", 9.5)"$9.50"
regexp_match regexp_match(text, pattern) Boolean True if text matches the regular expression pattern. regexp_match("a1b", "[a-z][0-9]")true
regexp_replace regexp_replace(text, pattern, replacement) Text Replaces all matches of pattern with replacement. regexp_replace("a1b2c", "[0-9]", "-")"a-b-c"

5.2 Math Functions (8)

FunctionSyntaxReturnsDescription & Example
abs abs(number) Number Absolute value. abs(-7)7
round round(number, decimals) Number Rounds to decimals places. round(3.456, 2)3.46
floor floor(number) Integer Rounds down to nearest integer. floor(3.9)3
ceiling ceiling(number) Integer Rounds up to nearest integer. ceiling(3.1)4
min min(a, b) Number Smaller of two values. min(5, 3)3
max max(a, b) Number Larger of two values. max(5, 3)5
sqrt sqrt(number) Decimal Square root. sqrt(16)4
mod mod(number, divisor) Number Remainder after division. mod(10, 3)1

5.3 Conditional Functions (2)

FunctionSyntaxReturnsDescription & Example
if if(condition, trueValue, falseValue) Varies Returns trueValue when condition is true, otherwise falseValue.
if(status = "paid", "Yes", "No")"Yes" or "No"
coalesce coalesce(value1, value2, ...) Varies Returns the first non-empty argument.
coalesce(nickname, firstName, "Unknown")

5.4 Null Functions (1)

FunctionSyntaxReturnsDescription & Example
isempty isempty(value) Boolean True if the value is null, empty string, or contains only whitespace.
isempty(middleName)true if blank

5.5 Date & Time Functions (12)

FunctionSyntaxReturnsDescription & Example
today today() Date Current date (no time component). today()2026-04-13
now now() DateTime Current date and time. now()2026-04-13T14:30:00Z
year year(date) Integer Extracts the year. year(today())2026
month month(date) Integer Extracts the month (1-12). month(today())4
day day(date) Integer Extracts the day of month (1-31). day(today())13
hour hour(dateTime) Integer Extracts the hour (0-23). hour(now())14
minute minute(dateTime) Integer Extracts the minute (0-59). minute(now())30
second second(dateTime) Integer Extracts the second (0-59). second(now())0
dayofweek dayofweek(date) Integer Day of week (1 = Sunday, 7 = Saturday). dayofweek(today())2 (Monday)
strftime strftime(date, format) Text Formats a date with a Unicode-style pattern. strftime(today(), "yyyy-MM-dd")"2026-05-15"
dateadd dateadd(date, amount, unit) Date Adds amount of unit ("day", "month", "year", "hour", "minute", "second"). dateadd(today(), 7, "day")
datediff datediff(start, end, unit) Integer Difference between two dates in the given unit. datediff(birthDate, today(), "year")

5.6 Conversion Functions (4)

FunctionSyntaxReturnsDescription & Example
getastext getastext(value) Text Converts any value to its text representation. getastext(42)"42"
getasnumber getasnumber(value) Number Extracts numeric value from text. getasnumber("$1,234.56")1234.56
getasdate getasdate(value) Date Parses text as a date. getasdate("2026-04-13") → date value
getasboolean getasboolean(value) Boolean Converts to boolean. Non-zero numbers, non-empty strings, and "true" yield true. getasboolean(1)true

5.7 Logical Functions (2)

FunctionSyntaxReturnsDescription & Example
case case(test1, result1, test2, result2, ..., default) Varies Evaluates tests in order, returns the result for the first true test. If none match, returns default.
case(status = "A", "Active", status = "I", "Inactive", "Unknown")
isvalid isvalid(value) Boolean Returns true if the value is valid (not an error result from a failed conversion or missing reference).
isvalid(getasnumber(userInput))

5.8 Crypto & Encoding Functions (13)

FunctionSyntaxReturnsDescription
md5md5(text)TextMD5 digest as lowercase hex.
sha1sha1(text)TextSHA-1 digest as lowercase hex.
sha256sha256(text)TextSHA-256 digest as lowercase hex.
sha384sha384(text)TextSHA-384 digest as lowercase hex.
sha512sha512(text)TextSHA-512 digest as lowercase hex.
hmacsha256hmacsha256(message, key)TextHMAC-SHA256 as lowercase hex.
base64encodebase64encode(text)TextStandard Base64 encoding.
base64decodebase64decode(text)TextStandard Base64 decoding.
base64urlencodebase64urlencode(text)TextURL-safe Base64 encoding.
hexhex(text)TextHex-encodes the input bytes.
uuiduuid()TextGenerates a new UUID v4.
urlencodeurlencode(text)TextPercent-encodes for use in URLs.
urldecodeurldecode(text)TextDecodes percent-encoded text.

5.9 Statistics Functions (13)

FunctionSyntaxReturnsDescription
meanmean(n1, n2, ...)NumberArithmetic mean.
medianmedian(n1, n2, ...)NumberMiddle value (or average of two middles).
modemode(n1, n2, ...)NumberMost-frequent value.
variancevariance(n1, n2, ...)NumberSample variance.
varpvarp(n1, n2, ...)NumberPopulation variance.
stdevstdev(n1, n2, ...)NumberSample standard deviation.
stdevpstdevp(n1, n2, ...)NumberPopulation standard deviation.
rangerange(n1, n2, ...)Numbermax - min.
percentilepercentile(array, p)Numberp-th percentile (0–100).
quartilequartile(array, q)Numberq-th quartile (0–4).
zscorezscore(value, array)NumberZ-score of value relative to the distribution.
correlcorrel(arrayX, arrayY)NumberPearson correlation coefficient.
covarcovar(arrayX, arrayY)NumberCovariance.

5.10 Advanced Math & Calculus (20)

FunctionSyntaxReturnsDescription
lnln(x)NumberNatural logarithm.
loglog(x, base?)NumberLogarithm with optional base (default 10).
log2log2(x)NumberBase-2 logarithm.
powpow(base, exponent)NumberExponentiation.
atan2atan2(y, x)NumberTwo-argument arctangent.
hypothypot(x, y)NumberHypotenuse (sqrt(x² + y²)).
factorialfactorial(n)Numbern!
combinationscombinations(n, k)NumberBinomial coefficient C(n,k).
permutationspermutations(n, k)NumberP(n,k).
gcdgcd(a, b)NumberGreatest common divisor.
lcmlcm(a, b)NumberLeast common multiple.
pipi()Numberπ.
ee()NumberEuler's number.
derivativeatderivativeat(expr, var, x)NumberNumerical derivative of expr in var at x.
integralintegral(expr, var, a, b)NumberDefinite integral on [a, b].
integralbetweenintegralbetween(arrayX, arrayY)NumberTrapezoidal integration of paired samples.
simpsonsimpson(expr, var, a, b, n)NumberSimpson's-rule integration with n subintervals.
slopeslope(arrayX, arrayY)NumberSlope of the linear regression line.
interceptintercept(arrayX, arrayY)NumberY-intercept of the linear regression line.

5.11 System / Get() Functions (4)

FunctionSyntaxReturnsDescription
getget(key)VariesReads a runtime system value by key (e.g. "currentUser", "projectName", "sessionID").
get_lastdialogbuttonget_lastdialogbutton()IntegerIndex (1, 2, 3) of the button the user clicked in the most recent dialog.
get_lastdialogbuttonlabelget_lastdialogbuttonlabel()TextLabel text of the button the user clicked in the most recent dialog.

5.12 Array Functions (19)

FunctionSyntaxReturnsDescription
arrayarray(v1, v2, ...)ArrayConstructs an array. Nestable for matrices: array(array(1,2), array(3,4)).
arraylengtharraylength(a)IntegerElement count.
arraygetarrayget(a, i, j?)VariesElement at index i (0-based), optional j for nested arrays.
arraysetarrayset(a, i, value)ArrayReturns a with index i replaced.
arraypusharraypush(a, value)ArrayAppends value.
arraypoparraypop(a)ArrayRemoves the last element.
arrayconcatarrayconcat(a, b)ArrayConcatenates two arrays.
arrayslicearrayslice(a, start, end?)ArraySub-array from start (inclusive) to end (exclusive).
arrayindexofarrayindexof(a, value)Integer0-based index, or -1 if not present.
arraycontainsarraycontains(a, value)BooleanTrue if the array contains value.
arrayjoinarrayjoin(a, separator)TextJoins elements with separator.
arraysplitarraysplit(text, separator)ArraySplits text into an array.
arraysortarraysort(a)ArrayReturns the array sorted ascending.
arrayreversearrayreverse(a)ArrayReturns the array reversed.
arrayuniquearrayunique(a)ArrayRemoves duplicates, preserving order.
arraysumarraysum(a)NumberSum of numeric elements.
arraymeanarraymean(a)NumberMean of numeric elements.
arrayminarraymin(a)NumberSmallest element.
arraymaxarraymax(a)NumberLargest element.
Tip: Functions can be nested freely. For example: upper(left(trim(firstName), 1)) & lower(middle(trim(firstName), 2, 100)) capitalizes only the first letter.

5.13 Dynamic & Metadata Functions (3)

FileMaker-parity primitives for runtime introspection and dynamic evaluation. Unlike the rest of the library, these functions need access to the current row, the metadata catalogue, and the project database, so they're registered per evaluation rather than as static library entries — but they look and call exactly the same way from a formula.

FunctionSyntaxReturnsDescription & Example
GetField GetField(name) The named field's value, coerced to the field's natural FormulaValue type Looks up a field on the current record by its internal name or display name. Useful for indirect references where the field name is data, not a literal. GetField("clientName") on a Jobs row returns the same value as clientName. Unknown names return null.
Evaluate Evaluate(formula) The result of the embedded formula, as text Compiles and runs an arbitrary formula string in the current row's context. Recursion is bounded by the same visiting-set that prevents calc-field cycles. Evaluate("totalBillable * 1.13") returns the post-tax billable. Use to embed formulas stored in data (e.g. user-defined "rule" fields).
ExecuteSQL ExecuteSQL(query, columnSep, rowSep, [args…]) Result rows joined by separators (text) Read-only SQL against the project database. The leading verb must be SELECT or WITH — anything else returns an empty string and logs at debug. Trailing args bind to ? placeholders in order. ExecuteSQL("SELECT count(*) FROM jobs WHERE status = ?", ",", "\n", "Completed") returns the count of completed jobs.
SELECT-only by design. ExecuteSQL rejects every statement that doesn't start with SELECT or WITH — no INSERT, UPDATE, DELETE, DROP, or ATTACH. A formula expression must never be able to mutate the schema or the data.
Use case: Aggregate across tables that aren't directly related. ExecuteSQL("SELECT sum(amount) FROM invoices WHERE customerid = ?", ",", "\n", customerID) sums every invoice for the customer on the current row, even when there's no native relationship wired up to support a Sum(Invoices::amount).

6. Script Engine

Scripts automate repetitive tasks in Structa. A script is an ordered sequence of steps (actions) that can manipulate records, navigate between forms, display dialogs, perform calculations, and more. Navigate to Scripts in the sidebar to manage them.

6.1 The Script Editor

The Script Editor has three panels:

Each step has an optional condition — a formula that is evaluated at runtime. If the condition evaluates to false, the step is skipped.

6.2 Variables

Scripts support local variables that persist for the duration of a script run. Variables are set with the setVariable step and referenced in formulas with the $ prefix:

// Set a variable
setVariable: name = "discountRate", value = 0.1

// Use in a later step's formula
setField: field = "totalPrice", value = subtotal * (1 - $discountRate)

Variable names are case-sensitive. Variables are scoped to the current script execution — they do not persist between runs or leak into other scripts (unless passed via performScript parameters).

6.3 Control Flow

Scripts support conditional execution and looping through step conditions and the following patterns:

6.4 Event Triggers

Scripts can be triggered automatically in response to events. Configure triggers in the script's properties:

TriggerFires When
On Record LoadA record is loaded into a form.
On Record CommitA record is about to be saved. Returning false cancels the save.
On Record DeleteA record is about to be deleted. Returning false cancels the deletion.
On Form OpenA form is opened or navigated to.
On Form CloseA form is about to close.
On TimerAt a specified interval (in seconds) while a form is open.

6.5 Error Handling

If a script step fails (e.g., deleteRecord on a locked record, or a calculation error), Structa halts the script and displays an error dialog. To handle errors gracefully, use step conditions to check for potential problems before they occur, or use isvalid() in formulas to test values before operating on them.

Note: Script execution logs are written to the project's logs/ directory. Review these for debugging failed script runs.

7. Script Step Reference

Structa provides 50 built-in script steps organized by category (navigation, record manipulation, field operations, control flow, data, window/UI, script flow, file, reports, dashboards, record locks, and miscellaneous).

7.1 Field Steps

StepParametersDescription
setField table, field, value (formula) Sets the specified field on the current record to the result of the value formula. The record must be in edit mode.
insertText table, field, text (formula) Appends the evaluated text to the end of the specified field's current value. Useful for building log fields or notes.

7.2 Record Steps

StepParametersDescription
newRecord table (optional, defaults to current) Creates a new blank record in the specified table and makes it the current record.
duplicateRecord table (optional) Duplicates the current record, including all field values except auto-increment and UUID fields.
deleteRecord table (optional), confirm (boolean) Deletes the current record. If confirm is true, shows a confirmation dialog first.
deleteAllRecords table, confirm (boolean) Deletes all records in the current found set. Requires confirmation by default.
commitRecord (none) Saves the current record, running all validation rules. Fails if validation errors exist.
revertRecord (none) Discards all uncommitted changes to the current record, reverting to the last saved state.

7.3 Navigation Steps

StepParametersDescription
goToLayout formID Navigates to the specified form. Works in the native runtime and, when triggered from a button in a published web form, instructs the daemon to 302-redirect the browser to that form's URL.
goToRecord target: "First", "Last", "Previous", "Next", or a record number / rowid Moves the current record pointer within the found set.
goToField fieldID Moves keyboard focus to the named field on the current form.
goToRelatedRecord relationship, targetForm (optional) Navigates to the related records for the current record, optionally opening them in a specified form.
goToDashboard dashboardID Switches the workspace to the named dashboard. Works from any mode and from server-side button scripts.

7.4 Search & Sort Steps

StepParametersDescription
performFind table, criteria (formula) Searches the specified table using the criteria formula and sets the found set to matching records.
showAllRecords table (optional) Clears the current search and shows all records in the table.
sortRecords fields (list), directions (ascending/descending per field) Sorts the current found set by the specified fields and directions.
unsortRecords (none) Removes any active sort, returning the found set to natural (insertion) order.

7.4a Control Flow Steps

StepParametersDescription
ifCondition / elseIfCondition / elseBlock / endIf condition (formula, for if & else-if) Branching control flow, FileMaker-style. Steps between matching endIf markers run only when the condition is true.
loop / exitLoopIf / endLoop condition (formula, on exitLoopIf) Bounded loop. loop opens, endLoop closes, exitLoopIf breaks early when its condition becomes true.

7.4b Lock Steps

StepParametersDescription
lockRecord tableID, rowid (optional — defaults to current row) Acquires an advisory lock on a row. Other clients see a padlock glyph and (by convention) refuse to edit. Pairs with ?skip_locked=1 on the JSON API to build worker queues.
unlockRecord tableID, rowid (optional) Releases a previously-acquired lock. Locks also expire automatically after their TTL.

7.5 Variable Steps

StepParametersDescription
setVariable name (text), value (formula) Creates or updates a script-local variable. Reference it later with $name.

7.6 Dialog & UI Steps

StepParametersDescription
showDialog title, message, buttons (list, e.g., ["OK", "Cancel"]) Displays a modal dialog with a title, message, and one or more buttons. The clicked button name is stored in $dialogResult.
refreshObject objectName (text) Forces a named form object to re-evaluate its data binding and redraw. Useful after changing data that a portal or calculated label depends on.

7.7 URL & File Steps

StepParametersDescription
openURL url (formula) Opens the evaluated URL in the system default browser.
openFile path (formula) Opens the file at the specified path using the system default application.
sendEmail to, subject, body (all formulas) Opens the system default email client with a pre-filled compose window.
beep (none) Plays the system alert sound. Useful for audible notifications in automated scripts.

7.8 Export & Import Steps

StepParametersDescription
exportRecordsCSV table, fields (list), filePath (formula) Exports the current found set (or all records) to a CSV file at the specified path.
importRecords table, filePath (formula), mapping (field map) Imports records from a CSV file into the specified table using the column-to-field mapping.
exportFieldContents field, filePath (formula) Exports the contents of a single field (typically an imageReference or longText) to a file.

7.9 Report Steps

StepParametersDescription
runReport reportID Generates the named report and opens it in a preview window.
printReport reportID Generates the named report and immediately opens the system Print panel (which also covers "Save as PDF").
Need to evaluate a formula and stash the result? Use setVariable — its value parameter takes any formula, including arbitrary expressions, function calls, and aggregates. There is no separate "runCalculation" step.

7.10 Script Flow Steps

StepParametersDescription
performScript scriptName (text), parameter (formula, optional) Calls another script by name. The optional parameter is available in the sub-script as $scriptParameter. Execution returns to the calling script when the sub-script completes.
exitScript result (formula, optional) Returns from the current script. If called from a sub-script, the optional result is available to the caller via Get("ScriptResult").
haltScript (none) Stops every script in the current call chain — even sibling scripts higher up the stack.
pauseScript seconds (number, optional) Suspends execution. With no argument, waits until the user resumes the runtime; with a number, pauses for that many seconds.

7.11 HTTP Integration Steps

Talk to external services from inside a script. Both steps run synchronously from the script's perspective — they block the calling script until the response arrives or the timeout elapses — and write the response into either a script variable (when the target starts with $) or a field on the current record.

StepParametersDescription
insertFromURL url, target (text), timeout (number, optional, sec) Issues a GET to the URL and writes the response body into the target. target with a leading $ writes to a script variable; otherwise it's the internal name of a field on the current record. Response is capped at 10 MiB; anything larger is truncated. Schemes other than http / https are rejected.
httpRequest url, method (GET / POST / PUT / PATCH / DELETE / HEAD), headers (multiline "Name: Value"), body (text), target, statusTarget (optional), timeout (optional) Full HTTP request with method, headers, body. Response body lands in target; statusTarget (if provided) receives the HTTP status code. Variables and field references in url / headers / body are substituted before sending, so "Authorization: Bearer $apiToken" works out of the box. Default Content-Type for non-empty bodies is application/json.
Example: Fetch a weather snapshot for the job's address and stash it on the record. Drop an insertFromURL step with:
url:    "https://api.weather.example.com/forecast?q=" & cityField
target: weatherSnapshot
The body of the response is written into the row's weatherSnapshot field, where it can be parsed by a calc field or displayed verbatim.
Timeouts and limits. Both steps cap the per-request timeout at 300 seconds and the response body at 10 MiB by default. Scripts that fetch larger payloads should set the maxBytes parameter explicitly. Connections that hang past the timeout are cancelled and surface a scriptRuntimeError the calling script can catch via Set Error Capture.

8. Form Designer

The Form Designer lets you create custom visual layouts for viewing and editing records. Navigate to Forms in the sidebar. Forms are bound to a single table and display one record at a time (or multiple records in a list/portal).

8.1 Form Objects

The object palette has 25 distinct types. Every type renders identically in the native runtime and on the web (see 11.7 HTML Rendering).

ObjectDescription
LabelStatic text or a formula-driven display.
Text FieldSingle-line editable input bound to a text, email, url, or longText field.
Text AreaMulti-line editable input for longText fields.
Number FieldNumeric input bound to an integer, decimal, or autoIncrementPrimaryKey field.
CheckboxToggle bound to a boolean field.
Radio GroupMutually-exclusive choice from a value list (static or table-backed).
DropdownCompact popup menu bound to a choice or foreignKey field.
Date PickerCalendar picker bound to a date field.
Time PickerClock picker bound to a time field.
Date-Time PickerCombined date + time picker bound to a dateTime field.
Image ViewDisplays an imageReference field, a static image from project assets, or an SF Symbol (rasterised server-side for the web).
ButtonTriggers a script when clicked. Same script runs on web buttons via /_action/:scriptID.
SeparatorHorizontal or vertical line for visual grouping.
Group BoxBordered container that visually corrals other objects under a title.
Tab ControlTabbed container that swaps which child objects are visible per active tab.
PortalDisplays related records from a child table in a scrollable list. Inline editing supported.
Web ViewEmbedded web content, useful for rendering HTML from a longText field.
ContainerGeneric layout container (used by templates and by the AI Assistant when composing complex layouts).
ChartBar, line, area, pie, doughnut, or scatter chart rendered by chart.js (see section 8.6).
Dashboard EmbedEmbeds an existing dashboard (KPIs, charts, recent records, notes) inside a form.
SpacerInvisible space used to push other objects apart during auto-layout.
RectangleDecorative shape with configurable fill and border.
Rounded RectangleDecorative shape with a corner radius.
EllipseDecorative oval / circle.
LineDecorative straight line with adjustable thickness and colour.

8.1a Form Sections (Bands)

Forms are organised into bands, analogous to FileMaker Pro's Part Setup. Open Form › Section Editor to manage them. Each band can be enabled or disabled, given an explicit height, and optionally pinned so it stays static when the user scrolls.

BandPurposePinnable
Master HeaderShared header that appears above every layout in the project (logo, global navigation).Yes
HeaderThis form's header. Page title, action buttons, filter chips.Yes
BodyThe primary content area. First-class draggable band — drop fields, portals, charts here.No
NavigationInline navigation strip between body and footer (typically pagination or step indicators).Yes
FooterThis form's footer. Save / Cancel buttons, status text.Yes
Master FooterShared footer for the project (legal copy, build info).Yes

Bands render in the same order both natively and in the published HTML. Each band's height is honoured exactly. Pinned bands stay fixed while the body scrolls.

8.2 Layout & Arrangement

Objects are positioned on a free-form canvas using absolute coordinates. The Form Designer provides alignment and distribution tools:

Select multiple objects with Shift+click or by dragging a selection rectangle. Use Cmd+G to group selected objects.

8.3 Data Binding

Each form object can be bound to a field from the form's table. Select an object, then in the Object Inspector, choose a Field from the dropdown. The object will display and edit the value of that field for the current record.

For labels, you can bind to a formula instead of a field. The formula is evaluated in the context of the current record.

8.4 Portals

A portal displays related records from a child table. To add a portal:

  1. Drag a Portal object onto the canvas.
  2. In the Object Inspector, select the relationship to follow.
  3. Choose which fields from the related table to display as columns.
  4. Optionally configure sorting and filtering for the portal rows.

Portals support inline editing if the related fields are editable. New related records can be created directly in the portal's last empty row.

8.5 Appearance & Behavior

Each object has appearance settings (font, font size, color, background, border, corner radius) and behavior settings (visible, enabled, tooltip). Appearance can be static values or formula-driven via conditional formatting rules.

Conditional Formatting Rules

Every form object has a Conditional Formatting Rules section in the Object Inspector. Each rule consists of:

Rules are stacked top-to-bottom and applied as a layered overlay: each rule that evaluates to true overwrites the attributes set so far. The last matching rule wins, so place broader rules first and more-specific overrides below them. Disabled rules are skipped. Conditional formatting renders identically in the native runtime and in published HTML forms.

Example: a Due Date label that turns red bold when overdue.

Rule 1
  Formula:    DueDate < today()
  Background: #FFE5E5
  Text:       #B00020
  Bold:       on
Tip: Rules persist per object and survive save/reopen. Use the legacy single-formula "Conditional" field for backward compatibility with older projects; the rules list is the modern, multi-rule replacement.

8.6 Charts

The Chart object renders interactive data visualizations using the bundled chart.js library. It runs inside a fully transparent WKWebView, so the chart floats over your form background (solid color, image, or gradient) with no borders or scrollbars.

Supported chart types:

Placing a chart:

  1. Drag the Chart object from the object palette onto the canvas.
  2. Open the Object Inspector. The Chart panel auto-selects the form's own table and offers sensible X-axis defaults.
  3. Tick one or more Y Fields in the multi-select list. The table and X-field defaults are persisted automatically on the first tick — the chart renders immediately.
  4. Adjust the chart type, title, aggregation (Sum, Average, Count, Min, Max, None), and per-series colors as needed.

Chart configuration fields:

SettingMeaning
TypeBar, Line, Area, Pie, Doughnut, or Scatter.
TitleOptional title displayed above the chart.
TableSource table. Defaults to the form's own table; pick any table in the project.
X FieldCategorical or date field used for the x-axis (or pie/doughnut slice labels).
Y FieldsNumeric fields (integer, decimal, or calculation). Each becomes a series; pie/doughnut charts use only the first.
AggregateHow to combine Y values for rows that share an X value: Sum, Average, Count, Min, Max, or None.
LimitMaximum number of source rows to read (default 100).
ColorsPer-series color picker. Blank slots fall back to the default 8-color palette.
Show legendHide or show the chart.js legend.
StackedStack series on top of each other (bar and area charts only).

Web-published charts: Published forms render identical charts client-side. The same chart.js library that runs in-app is served from /structa-static/chart.js, so your web users don't fetch it from a CDN and the chart looks exactly like it does on your Mac.

chart.js is a pinned asset. Structa ships with a fixed version of chart.js baked into the app bundle. It is never updated at runtime — only when you install a new version of Structa. This guarantees your charts keep rendering exactly the same way across launches and across the LAN.
Tip: Want a chart that breaks down records by category? Set X Field to the category column (e.g. status), Y Field to any column, and pick Aggregate: Count. Each category becomes a bar, sized by how many records match.

9. Data Browser

The Data Browser is your primary interface for viewing and editing data. Navigate to Browse Data in the sidebar (or press Cmd+Shift+R).

9.1 Grid View

The default view displays records as rows in a spreadsheet-like grid. Each column corresponds to a field. You can:

A form view is also available — click the Form View toggle in the toolbar to switch between grid and the default form for the current table.

Click the search field in the toolbar (or press Cmd+F) to search. Type a search term to perform a quick search across all text fields. For more precise searching, use search operators:

Full-text indexing. Quick search is backed by SQLite's FTS5 index. The first search on a new table transparently builds the index (in a single transaction) so subsequent searches return near-instantly, even on 50k+ row tables. The index is kept in sync as you add, edit, and delete records; searches with a leading wildcard (e.g. *smith) automatically fall back to LIKE scans, since FTS5 can't use an index for those.

OperatorExampleDescription
==SmithExact match.
====smithExact match, case-sensitive.
*Sm*Wildcard (zero or more characters).
> < >= <=>100Numeric or date comparison.
..100..500Range (inclusive).
!!SmithNot equal.
##Field is empty.

9.3 Sorting & Filtering

Click a column header to sort by that field (click again to reverse). Hold Shift and click additional column headers to add multi-level sorts.

Use Records > Sort Records... to configure a custom sort order with multiple fields and ascending/descending per field.

9.4 Creating & Editing Records

Reports

The Reports workspace turns table data into printable, shareable documents. Open it from the sidebar's Reports entry.

Reports persist alongside the project, so a saved report definition reproduces the same output as the underlying data evolves.

Dashboards

Dashboards are at-a-glance pages combining KPIs, charts, recent-record lists, and notes. Each project can have any number of dashboards, selectable from the sidebar's Dashboards section.

Tile Types

Refresh & Performance

Each dashboard has its own refresh timer (5 s – 5 min, or manual). Every refresh batches the SQL work: all KPI tiles backed by the same table are computed in one SQL round-trip, not one per tile. A 12-tile dashboard against the Jobs table runs one query, not twelve — so refresh latency is essentially independent of tile count. The result of each aggregate is cached briefly so a tile that the user nudges twice doesn't re-hit SQL.

Configuration

AI Assistant

The AI Assistant is a chat surface that can answer questions about the project's schema, suggest formula expressions, and draft scripts. It runs against your configured AI provider (configured in Settings > AI).

The assistant has read-only access to the project's metadata (table names, field names, relationship structure). It does not transmit row data unless you explicitly paste it into the conversation.

10. Import & Export

10.1 CSV Import & Export

Import: Use File > Import > CSV... to import a CSV file into a table. The import wizard lets you:

  1. Select the target table (or create a new one from the CSV headers).
  2. Map CSV columns to table fields.
  3. Preview the first 100 rows before importing.
  4. Choose how to handle duplicates (skip, update, or create new).

Column type inference samples the first 200 rows of the file (not the whole thing), so even multi-million-row CSVs preview quickly. The actual write pipeline uses a single prepared INSERT statement per batch of 500 rows, giving dramatically faster bulk loads compared to per-row inserts.

Export: Use File > Export > CSV... to export the current found set. Choose which fields to include, the delimiter (comma, tab, semicolon), and the text encoding (UTF-8 or ISO-8859-1). Export streams rows from SQLite straight to disk, so memory stays flat regardless of table size.

10.2 FileMaker Import

Structa can import FileMaker Pro data in two ways. When you pick FileMaker as the import source, Structa's import screen explains the trade-offs inline. Here is the fuller picture:

10.2.1 Binary .fmp12 import

Structa reads the .fmp12 file format directly — no FileMaker installation required. The binary importer recovers:

What the binary importer cannot recover, because FileMaker does not encode it in a reversible way:

10.2.2 DDR XML import (recommended for a full migration)

For a complete migration, use FileMaker's Database Design Report export:

  1. In FileMaker Pro, open your file and choose Tools → Database Design Report…
  2. Select XML as the output format.
  3. Import the generated .xml file in Structa via File > Import > FileMaker…

The DDR XML path imports tables, fields, records, the full relationship graph (with cardinality), scripts, value lists, and layout metadata.

Tip: If you only need the data, the binary .fmp12 importer is faster and requires no FileMaker installation. If you want a faithful port of your FileMaker solution, go DDR XML.

10.3 Advanced Import/Export

Structa also supports advanced import/export workflows:

11. Web Publishing

Structa ships with an embedded HTTP daemon. Flip a single switch and your project is online — every form you've designed becomes a working web page, every script-bound button becomes a working web button, and every list view becomes a sortable, searchable HTML grid. Browser clients require an active Web Sharing subscription (Apple in-app purchase — see Pricing).

One layout, two surfaces. Structa's design goal is total parity: every form, whether you built it from scratch, started from a template, or had the AI Assistant generate it, renders identically natively and on the web. Same bands, same coordinates, same fonts, same buttons, same scripts firing on click. SF Symbol icons, embedded images, conditional formatting, form backgrounds, chart.js charts, all 25 form-object types — all crossing over without re-authoring.

11.1 Per-Project Sharing Toggles

Each project carries its own sharing state. Open Sharing › Settings to flip:

State persists inside the .structa package, so the next time you (or another machine) opens the project, the daemon comes up with the same toggles you left it with.

The Sharing dashboard shows the real LAN IP for each share URL (e.g. http://192.168.1.75:8080/<slug> rather than 0.0.0.0:8080) so you can copy a URL that works directly from another device on your network. The Sharing toolbar banner uses the same address.

11.2 Auto-Publish Profiles

Every form in your project gets a publish profile automatically the moment it's created — whether from the Form Designer, from a template, or from the AI Assistant. There is no per-form "Publish…" step. The profile's URL slug is derived from the form name; the route handles list, detail, create, update, and delete out of the box.

If you open a project that pre-dates auto-profiles (or one imported from a template), Structa runs a one-time backfill on attach so every form ends up with a working route.

11.3 Routes

Routes are mounted under a per-project slug. For a project slugged jobs and a form slugged active-jobs:

URLServes
/Daemon lobby — lists every project where "Listed in publishing root" is on.
/jobsProject root — redirects to the project's default form (set in Sharing Settings) or shows a per-project list of forms.
/jobs/active-jobsHTML list view of the form's table.
/jobs/active-jobs/:idHTML detail view for one record. List forms with a default form configured redirect here from the list view.
/jobs/active-jobs/_action/:scriptIDPOST endpoint that runs a script. Form buttons bound to a script POST to this endpoint and the daemon honours the script's NavigationRequest (Go to Form / Table / Record / Dashboard) by 302-redirecting the browser to the matching URL.
/jobs/active-jobs/_lockPOST / DELETE — advisory record lock acquire / release. POST /_lock/release aliases DELETE for clients that can only call sendBeacon.
/jobs/active-jobs/_assets/…Static route for container-field attachments (PDFs, photos, audio).
/jobs/api/v1/<table>JSON API — GET list / detail, POST create, PUT update, DELETE. Supports ?skip_locked=1 for queue workers.

11.4 Server-Side Button Scripts

Native form buttons bound to a script work the same way on the web. The HTML renderer emits a real <button type="submit"> whose form posts to /_action/:scriptID. The daemon runs the script using the current record as context. If the script returns a NavigationRequest, the daemon maps it through the publish-profile registry and 302-redirects the browser:

Field edits flow through the same /_action/ mechanism when auto-save is on; otherwise the user explicit-Saves to fire the update.

11.5 Authentication

Structa supports three authentication modes per publish profile:

ModeDescription
NoneNo authentication. All routes are public (best for read-only catalogue or localhost-only).
Basic AuthHTTP Basic Authentication. Users are defined in the publish profile with a username and password.
Session AuthCookie-based sessions with a login page. Users log in with credentials and receive a session token stored in a secure HttpOnly cookie. Sessions are backed by SQLite. Bearer tokens also accepted on /api/v1/… JSON routes.

Users and roles are managed in the Security workspace (left sidebar › Configuration). See Section 14 — Security & Accounts for the full account / privilege-set model.

Per-project guest controls. The per-project Web Guest toggle (Sharing dashboard) overrides the profile's requiresAuth setting at the project level. When Guest is on, the four global CRUD flags (Read / Create / Edit / Delete) gate which verbs unauthenticated visitors may invoke. Mutating handlers (POST / PUT / DELETE / form submit) check the matching flag before running and return 403 when denied. Read endpoints follow the Read flag the same way.

11.6 Web Home Page Styles

The daemon's lobby (/) renders in one of five distinct visual styles. Pick one in Sharing › Settings › Home Page:

StyleVibeBest for
ClassicClean grid of tiles with a header strip.Internal team page, mixed projects.
MarqueeFull-bleed hero with marching tile carousel.Single flagship project, marketing-feel landing.
TerminalMonospaced, dark, command-line aesthetic.Internal tooling, dev / data-engineering shops.
BoardwalkWide horizontal card row, soft pastel palette.Customer-facing catalogue.
ReceptionCentred hero with a quiet card grid below.Front-of-house, single point of entry.

Upload a company logo at the top of the same panel. The logo appears on the lobby page and on the per-project shell. Logos are stored inside the daemon's appearance bundle, not the .structa project, so a single Mac hosting multiple projects only has to set them once.

11.7 HTML Rendering

The HTML renderer mirrors the native form renderer object-for-object:

11.8 Webhooks

Each publish profile carries a list of outbound webhooks. Whenever a record is inserted, updated, or deleted — from the native app, the data browser, a script, or a web client — Structa POSTs a JSON envelope to every matching webhook URL.

Each webhook entry has:

The JSON envelope:

{
  "event": "insert" | "update" | "delete",
  "table": "<sanitizedTableName>",
  "tableID": "<table-uuid>",
  "rowid": "<rowid>",
  "values": { "<column>": <value>, … }
}

Delivery is fire-and-forget on a serial utility queue with a five-second timeout and one retry on transport failure or 5xx response. Misconfigured webhooks log an error and never block the record write. Schemes other than http / https are rejected without firing a request.

11.9 Responsive — phones & tablets

Published forms adapt automatically to the viewing device, in three layered passes:

Detection precedence:

  1. ?layout=phone | tablet | desktop in the URL — explicit override. Handy for QA on a desktop browser.
  2. Sec-CH-UA-Mobile: ?1 client hint — modern browsers.
  3. User-Agent heuristic (matches "iPhone", "Android", "Mobi", "Windows Phone"; iPad is detected separately as a tablet).
  4. Default: desktop.
Preview on phone from your desk: append ?layout=phone to any form's URL in a desktop browser. The page renders exactly as a phone client would see it, with the stacked layout and 44 px touch targets.

12. Host Mode

Host Mode turns your Mac into a database server, allowing other Structa users on your local network to connect to your project from their own Macs. Additional native client seats require a Native CAL subscription (1, 3, 5, 10, 15, or 20 seats — see Pricing).

12.1 Setup

  1. Open your project and switch to Host mode (View > Mode > Host, or the toolbar toggle).
  2. Select a publish profile (or create one if none exists).
  3. Click Start Server.
  4. Structa displays the local URL (e.g., http://192.168.1.100:8080) that other devices can use to connect.
Tip: For deploying on a dedicated machine, consider using an older Mac as a server. Structa runs efficiently on any Mac that supports macOS 13 or later.

12.2 The Dashboard

The Host Mode dashboard shows real-time information:

12.3 LAN Access

By default, the server is accessible to any device on the same local network. Ensure your Mac's firewall allows incoming connections on the configured port. For access from outside your network, configure port forwarding on your router.

Warning: Exposing your database to the internet requires careful security configuration. Always enable authentication and consider using HTTPS.

12.4 HTTPS

Structa supports HTTPS with TLS certificates for encrypted connections. To enable HTTPS:

  1. In the publish profile, toggle HTTPS on.
  2. Provide a certificate file (.pem) and private key file (.pem), or let Structa generate a self-signed certificate for development/testing.
  3. Restart the server. The URL will change to https://.

Self-signed certificates will trigger browser warnings. For production use, obtain a certificate from a certificate authority (e.g., Let's Encrypt).

13. Multi-User Collaboration

When Host Mode is active, multiple users — on Macs running Structa or in any modern browser — can work in the same project at once. Structa coordinates them in two tiers: live updates (Tier 1) and presence + advisory locks (Tier 2).

13.1 Tier 1 — Live Record Refresh

Every connected client opens a WebSocket to the daemon. The daemon broadcasts a change event whenever any record is inserted, updated, or deleted — whether the write came from the native app, the data browser, a script, a web client, or a JSON API call. Every other client receives the broadcast and re-fetches the affected row, so the form you're looking at always reflects what's actually in the database.

This is on by default whenever the daemon is running; no per-form opt-in. Clients fall back to a slow-poll if their WebSocket drops.

13.2 Tier 2 — Presence & Advisory Locks

The same WebSocket carries presence and lock events:

13.3 Native Lock & Skip-Locked

The native runtime exposes locks to scripting:

13.4 Conflict Resolution

If two users manage to submit changes to the same record (e.g., due to network latency or a manually overridden advisory lock), Structa uses an optimistic concurrency model:

  1. Each record has a version counter that increments on every save.
  2. When a user submits changes, Structa checks that the record version matches what the user loaded.
  3. If the version has changed, the save is rejected with a conflict error, and the user is shown both the current server values and their submitted values.
  4. The user can choose to overwrite, merge, or discard their changes.

13.5 Audit Logging

Structa logs every record creation, update, and deletion with the user, timestamp, table, record ID, and changed fields. View the audit log from Settings › Audit Log or query it programmatically via the JSON API. The log persists inside the .structa package, so it survives daemon restarts and Mac reboots.

13.6 Open Remote Project

Use File > Open Remote Project… (⇧⌘O) to connect this Mac to another Mac running Structa in Host Mode. Enter the host’s URL (e.g. structa+mu://192.168.1.75:8080/my-project), your username, and your password. The remote session establishes against /auth/login, claims a native CAL seat from the host’s pool, and parks the session on the workspace so all subsequent reads and writes flow over the multiuser protocol.

When a remote write collides with another client’s edit (HTTP 409 + OCC token mismatch), the conflict resolver appears automatically: yours vs. theirs vs. base for each diverged field, with per-field Keep Yours / Accept Theirs / Use Base radios. The resolved values are re-PUT against the server’s newer OCC token so the merged record commits cleanly.

14. Security & Accounts

Structa ships with a built-in security model: named user accounts with hashed passwords, three baked-in privilege sets, a native sign-in gate on project open, and per-project guest controls with explicit CRUD permissions. Every layer is optional — an empty user table means the project opens straight to the workspace and the embedded server serves anonymous traffic.

14.1 The Security Workspace

Click Security in the left sidebar (under Configuration) to open the workspace. It has two tabs:

14.2 Privilege Sets

A privilege set bundles four kinds of permission:

The system sets are marked isSystem: true and reseed if you delete them. Your custom sets persist normally.

14.3 Native Sign-In Gate

When you open a project that has at least one active user account and the project’s native guest toggle is off, Structa presents a sign-in sheet before the workspace renders. Enter your username and password; the credentials are verified against the user table’s PBKDF2 hash in constant time. On success, the workspace appears and AppState.currentUser populates so scripts and ACL checks see the acting user. Cancel closes the project without unlocking it.

Projects with no accounts (or with the native guest toggle on) skip the gate entirely and open straight to the workspace.

14.4 Guest Access & CRUD Permissions

Each project carries four independent guest controls, visible in Sharing for any hosted project:

The two surface toggles are independent — you can run a project guest-readable on the web while requiring sign-in on native, or vice-versa. The four CRUD flags apply globally across every table; per-table refinement is available through named accounts + privilege sets.

Enforcement: every published-form handler declares its verb (create / edit / delete / default read); when a request resolves to guest, the matching flag is consulted before the handler runs. Disallowed verbs return HTTP 403 with a human-readable message.

14.5 Encryption at Rest

New projects encrypt at rest with AES-GCM. The symmetric key is generated at project creation and stored in your macOS Keychain; the on-disk archive is compressed with Apple’s LZFSE for a 2–4× smaller footprint. Decryption happens once at open; the plaintext lives on disk only during the session and re-encrypts on close.

15. Templates

15.1 Bundled Templates

Structa ships with 17 ready-to-use templates accessible from the onboarding wizard or File > Apply Template…. Every template is JSON-defined (no compiled Swift), includes a hero-banded form layout that renders identically on the web and on mobile, ships with publish profiles enabled, and includes sample data.

TemplateTablesWhat it’s for
Contact UsSubmissionsPublic-facing contact form for a website.
Newsletter SignupSubscribersEmail list capture with double-opt-in fields.
Support TicketTicketsInbound IT/customer support intake.
Bug ReportReportsStructured bug intake with severity, steps to reproduce, environment.
Customer ComplaintComplaintsFormal complaint capture for service businesses.
Customer FeedbackFeedbackNPS-style satisfaction survey.
Liability WaiverWaiversSigned-waiver capture for events and activity providers.
Job ApplicationCandidates, Experience, EducationRelational employment application with work history.
Volunteer ApplicationVolunteersCharity / event volunteer intake with availability.
Vendor RegistrationVendorsSupplier onboarding with W-9 / banking fields.
Leave RequestRequestsEmployee time-off request with approval workflow.
IT Support RequestRequestsInternal IT help desk intake.
Appointment BookingAppointmentsSelf-serve appointment scheduler.
Patient IntakePatients, History, MedicationsHealthcare new-patient onboarding.
Site InspectionInspections, FindingsField inspector checklist with photo attachments.
Event RegistrationEvents, Sessions, Attendees, Registrations4-table relational event signup.
Product OrderCustomers, Products, Orders, Line Items4-table relational order capture with line items.

Templates are organized under Resources/Templates/structa_templates/ as JSON files and loaded by JSONTemplateLoader. The Apply Template sheet shows previews, sample data toggles, and the per-template hero-band accent color.

15.2 Custom Templates

To create your own template from an existing project:

  1. Open the project you want to use as a template.
  2. Go to File > Export Template...
  3. Choose whether to include sample data or export schema only.
  4. Enter a template name, description, and icon.
  5. Save. The template is exported to the project's templates/ directory.

To import a custom template, use File > Import Template... and select the template file. It will appear in the onboarding wizard alongside the bundled templates.

16. Pricing

Structa is a free download with the full database builder, scripting, calculation engine, form designer, and all bundled templates included. Two optional in-app subscriptions add multi-user access on top of that base.

16.1 What's Included Free

FeatureIncluded Free
Database Builder (Tables, Fields, Relationships)Yes
Calculation Engine (118 functions)Yes
Script Engine (52 steps)Yes
Form Designer + RuntimeYes
Reports & DashboardsYes
Data BrowserYes
FileMaker .fmp12 ImportYes
SQL Dump & CSV Import/ExportYes
Bundled Templates (CRM, Inventory, Invoicing, Job Tracking, Notes)Yes
Single-User Use (host machine, owner seat)Yes

16.2 In-App Subscriptions

Two independent subscription groups gate multi-user publishing:

SubscriptionSeatsSurface
1-User Pack1 additional seatNative (LAN)
3-User Pack3 additional seatsNative (LAN)
5-User Pack5 additional seatsNative (LAN)
10-User Pack10 additional seatsNative (LAN)
15-User Pack15 additional seatsNative (LAN)
20-User Pack20 additional seatsNative (LAN)
Web UnlimitedUnlimited browsersWeb (LAN)
Tip: Prices are localised by Apple and shown in your store's currency. The Sharing screen reads them live from StoreKit and computes savings against the equivalent per-seat 1-User Pack cost. The host is responsible for exposing the daemon's port to the desired clients (port-forwarding, Tailscale, or other tunnels).
Manage your subscription via App Store > Account > Subscriptions. Apple handles billing, refunds, and cancellations; Structa unlocks the matching seats automatically once a purchase, restore, or revoke is reported by StoreKit.

17. Keyboard Shortcuts

File & Project

ActionShortcut
New ProjectCmd+N
Open ProjectCmd+O
Close ProjectCmd+Shift+W
SaveCmd+S
Import Database…Cmd+Shift+I
Apply Template…Cmd+Shift+T
Manage Security…Cmd+Shift+K
Manage Value Lists…Cmd+Shift+V
UndoCmd+Z
RedoCmd+Shift+Z
PreferencesCmd+,

Mode Switching

ActionShortcut
Design ModeCmd+1
Runtime ModeCmd+2
Host ModeCmd+3

Design Mode Sub-Routes

ActionShortcut
TablesCmd+Ctrl+T
RelationshipsCmd+Ctrl+E
FormsCmd+Ctrl+F
ScriptsCmd+Ctrl+S

Other Navigation

ActionShortcut
Raw Data (Runtime)Cmd+Shift+R
Publish ProjectCmd+Shift+P
Keyboard Shortcuts (this sheet)Cmd+/

Data Browser & Runtime Form

ActionShortcut
New RecordCmd+N
Duplicate RecordCmd+D
Delete RecordCmd+Delete
Find / SearchCmd+F
Show All RecordsCmd+Shift+F
Sort RecordsCmd+Shift+S
Next RecordCtrl+Down
Previous RecordCtrl+Up
Print Form (current record, detail view)Cmd+P
Commit RecordReturn
Revert RecordEscape

Documentation Window

ActionShortcut
Find in Docs (filter + highlight matches)Cmd+F
Clear search / restore full viewEsc

Form Designer

ActionShortcut
Select All ObjectsCmd+A
Group ObjectsCmd+G
Ungroup ObjectsCmd+Shift+G
Bring to FrontCmd+Shift+]
Send to BackCmd+Shift+[
Toggle Snap to GridCmd+'
Preview FormCmd+Shift+P

Script Editor

ActionShortcut
Run ScriptCmd+R
Add StepCmd+Shift+N
Delete StepCmd+Delete
Move Step UpCmd+Option+Up
Move Step DownCmd+Option+Down

18. Troubleshooting

18.1 Common Issues

Database Locked Error

This occurs when another process or a previous Structa instance has an open connection to the SQLite database. Solutions:

  • Close any other Structa windows that have the same project open.
  • Check Activity Monitor for zombie Structa processes and force-quit them.
  • Restart your Mac if the lock persists (rare, but clears stale file locks).
  • If the project is on a network drive or cloud-synced folder (Dropbox, iCloud), move it to a local directory. Network file systems can cause persistent lock issues.
Schema Mismatch After Update

If you see "Schema mismatch" after updating Structa, the project's internal tables need migration. Structa normally handles this automatically, but if it fails:

  • Make a copy of the .structa package before proceeding.
  • Open the project. If prompted about migration, click "Migrate Now".
  • If migration fails, restore from a snapshot (the snapshots/ directory inside the package).
  • Contact support with the error details if automatic migration cannot complete.
Import Errors (CSV or FileMaker)

Common import issues and solutions:

  • Encoding errors: Ensure your CSV is saved as UTF-8. Re-export from the source application with UTF-8 encoding selected.
  • Column count mismatch: Check for unescaped commas or newlines within field values. Use a text editor to verify the CSV structure.
  • Type conversion failures: Numeric fields with currency symbols or thousands separators may fail. Clean the data before import or import as text and convert later.
  • FileMaker .fmp12 not recognized: Ensure the file is from FileMaker Pro 12 or later. Earlier .fp7 files are not supported.
Server Won't Start (Host Mode)

If the Host Mode server fails to start:

  • Port in use: Another application is using the configured port. Change the port in the publish profile or stop the conflicting application.
  • Firewall blocking: macOS may be blocking incoming connections. Check System Settings > Network > Firewall and allow Structa.
  • Certificate error (HTTPS): If using HTTPS, verify that the certificate and key files are valid PEM format and that the key is not password-protected.
  • Permission denied: Ports below 1024 require administrator privileges. Use a port above 1024 (the default 8080 is recommended).
Calculation or Formula Errors

If a calculation field shows an error or unexpected result:

  • Circular dependency: Two or more calculation fields reference each other. Structa detects this and shows a "Circular reference" error. Restructure your formulas to break the cycle.
  • Type mismatch: Trying to perform arithmetic on text values or string operations on numbers. Use conversion functions (getasnumber, getastext) to coerce types.
  • Null reference: A field referenced in the formula is empty. Use coalesce() or isempty() to handle null values gracefully.
  • Related field not found: Verify the relationship exists and the field name in the TableName::fieldName reference is correct.
Performance Issues with Large Datasets

Structa uses SQLite, which handles millions of records efficiently. If you experience slowness:

  • Check if you have many calculation fields that reference related tables — each one triggers additional queries.
  • Ensure frequently searched fields have unique or indexed validation rules.
  • Avoid keeping the entire record set visible in the Data Browser — use searches to narrow the found set.
  • Move the project to an SSD if it is on a mechanical hard drive.

Structa's runtime layers several caches and streaming paths to stay fast on larger datasets. A few behaviors worth knowing:

  • Search: quick-search uses FTS5 after a one-time index build per table. Leading-wildcard searches (e.g. *smith) bypass the index and scan with LIKE.
  • Dashboards: every KPI tile on a given table is computed in a single SQL round-trip; results are cached for 2 seconds. A data edit invalidates the cache so the next refresh is fresh.
  • Data Browser paging: page rows and total count come from one SELECT using COUNT(*) OVER().
  • Lock glyphs: the lock-state query runs on a background task so it never stalls the grid; results are cached for 1.5 seconds.
  • Formulas: visibility, enabled, and conditional-format formulas are parsed once per session and their evaluation context is built once per form render.
  • CSV: import samples the first 200 rows for type inference and uses a prepared INSERT reused per 500-row batch; export streams rows straight to disk.

18.2 Crash Reports

If Structa crashes, macOS generates a crash report. To find and submit it:

  1. Open Console.app (in Applications > Utilities).
  2. In the sidebar, navigate to Crash Reports.
  3. Look for entries named "Structa" with the date/time of the crash.
  4. Click the report to view it, then use File > Save to export.

You can also find crash logs at ~/Library/Logs/DiagnosticReports/. When contacting support, include the crash report along with:

Note: Structa creates recovery snapshots before autosave. After a crash, reopen the project — if a snapshot is newer than the last save, Structa will offer to restore it.

18.3 Contact the Developer

For suggestions, bug reports, or complaints, choose Help > Contact the Developer… from the menu bar. A panel opens with:

Press Send. Structa opens your default email client with the message and attachments already in place. If no email client is configured, Structa writes the message and attachments to a folder on your Desktop and opens a pre-filled email with instructions — so the feedback path always works.

Tip: Bug reports are much easier to act on with a screenshot. If the problem is visual, include one; if it's a crash, please also attach the crash report from section 17.2.

Structa v2.2 — Documentation © 2026 FI Forensic Intelligence, Ltd. All rights reserved.