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:
- Double-click a
.structafile in Finder. - Use File > Open Project... (Cmd+O).
- Choose from File > Recent Projects.
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.
1.3 Creating Your First Project
- Launch Structa. The Welcome screen appears.
- Click "New Project" (or press Cmd+N). Choose a location and name, then click Save.
- Navigate to Tables in the sidebar.
- Click "Add Table" and enter a display name (e.g., "Contacts").
- Add fields by clicking the "+" button: First Name (Text), Last Name (Text), Email (Email), Phone (Text).
- Save with Cmd+S.
- Switch to Data Browser (Cmd+Shift+R) and start adding records.
1.4 The Interface
| Section | Items | Purpose |
|---|---|---|
| Project | Welcome | Landing screen with quick actions and recent projects. |
| Design | Tables, Relationships, Forms, Scripts | Define schema, layouts, and automation. |
| Data | Browse Data | View, search, create, edit, and delete records. |
| Deploy | Publish, Host Mode | Publish to the web or run as a network service. |
| Configuration | Settings | Application and project preferences. |
1.5 Application Modes
Structa operates in one of three modes, switchable from the Runtime menu or View > Mode:
| Mode | Purpose |
|---|---|
| Design | Create and modify tables, forms, scripts, and relationships. |
| Runtime | Use the database day-to-day: browse data, enter records, run scripts. |
| Host | Start 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:
| Type | SQLite Storage | Description |
|---|---|---|
text | TEXT | Single-line string, up to 255 characters by default. |
longText | TEXT | Multi-line string with no practical length limit. Renders as a text area. |
integer | INTEGER | Whole number (64-bit signed). |
decimal | REAL | Floating-point number (64-bit double). |
boolean | INTEGER (0/1) | True or false. Renders as a checkbox. |
date | TEXT (ISO 8601) | Calendar date without time (e.g., 2026-04-13). |
time | TEXT (HH:MM:SS) | Time of day without date. |
dateTime | TEXT (ISO 8601) | Combined date and time with timezone. |
uuid | TEXT | Auto-generated UUID v4 on record creation. |
autoIncrementPrimaryKey | INTEGER | Auto-incrementing integer. Typically used as a user-visible primary key separate from _rowid. |
foreignKey | INTEGER | References another table's primary key. Used to create relationships. |
calculation | Varies | Computed dynamically from a formula. Not stored on disk — evaluated at read time. |
json | TEXT | Arbitrary JSON data. Validated on save to ensure well-formed JSON. |
imageReference | TEXT | Path to an image file in the project's assets/ directory. Path-only; the bytes live wherever the path resolves to. |
container | TEXT (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. |
email | TEXT | Email address with basic format validation. |
url | TEXT | URL string with protocol validation. |
choice | TEXT | Single 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:
| Rule | Applies To | Description |
|---|---|---|
| Required | All types | Field must have a non-empty value. |
| Unique | All types | No two records may share the same value in this field. |
| Min Length | text, longText, email, url | Minimum character count. |
| Max Length | text, longText, email, url | Maximum character count. |
| Min Value | integer, decimal | Minimum numeric value. |
| Max Value | integer, decimal | Maximum numeric value. |
| Pattern (Regex) | text, email, url | Regular expression the value must match. |
| In List | All types | Value must be one of an explicitly enumerated set (also used to back choice-field dropdowns). |
| Not In List | All types | Value must NOT be in the given set (reserved-word lists, banned strings, etc.). |
| Custom Formula | All types | A formula returning a boolean. Reference other fields in the same record; failure shows the formula's name to the user. |
| Email Format | text, email | Strict email pattern (more rigorous than the cosmetic check on the email type). |
| URL Format | text, url | Must parse as a syntactically valid URL with a scheme. |
| Date Range | date, time, dateTime | Value must fall between an inclusive lower and upper bound (either side optional). |
| File Extension | container | Uploaded 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:
- A recovery snapshot is created before any changes are made.
- Structa uses SQLite's
ALTER TABLEwhen possible (adding columns, renaming columns). - 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.
- Foreign key relationships and indexes are rebuilt as needed.
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.
3.2 Cardinality Types
| Cardinality | Symbol | Description |
|---|---|---|
| One-to-One | 1:1 | Each record in Table A matches exactly one record in Table B. Enforced with a UNIQUE constraint on the foreign key. |
| One-to-Many | 1:N | One record in Table A can relate to many records in Table B. The most common type. |
| Many-to-One | N:1 | Many 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-Many | N:M | Records on both sides can relate to multiple records on the other. Requires a join table with two foreign keys. |
3.3 Creating Relationships
- Ensure at least one table has a
foreignKeyfield pointing to another table. - In the Relationship Designer, click "Add Relationship".
- Select the source table and its foreign key field.
- Select the target table (the table being referenced).
- Choose the cardinality (One-to-One, One-to-Many, or Many-to-Many).
- Optionally enable Cascade Delete (deleting a parent automatically deletes related children).
- 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:
- With Cascade Delete enabled: Delete all child records automatically.
- Without Cascade Delete: Show an error and prevent the deletion until all child records are removed or reassigned.
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:
- String literals use double quotes:
"hello" - Number literals are bare:
42,3.14 - Boolean literals:
true,false - Field references use the field's internal name:
firstName,totalAmount - Related field references use dot notation:
Company::name - Function names are case-insensitive:
Upper()=upper() - Parentheses control evaluation order:
(price + tax) * quantity
4.2 Operators
| Category | Operators | Example |
|---|---|---|
| Arithmetic | + - * / % | price * 1.08 |
| Comparison | = != < > <= >= | age >= 18 |
| Logical | AND OR NOT | status = "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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description & 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)
| Function | Syntax | Returns | Description |
|---|---|---|---|
md5 | md5(text) | Text | MD5 digest as lowercase hex. |
sha1 | sha1(text) | Text | SHA-1 digest as lowercase hex. |
sha256 | sha256(text) | Text | SHA-256 digest as lowercase hex. |
sha384 | sha384(text) | Text | SHA-384 digest as lowercase hex. |
sha512 | sha512(text) | Text | SHA-512 digest as lowercase hex. |
hmacsha256 | hmacsha256(message, key) | Text | HMAC-SHA256 as lowercase hex. |
base64encode | base64encode(text) | Text | Standard Base64 encoding. |
base64decode | base64decode(text) | Text | Standard Base64 decoding. |
base64urlencode | base64urlencode(text) | Text | URL-safe Base64 encoding. |
hex | hex(text) | Text | Hex-encodes the input bytes. |
uuid | uuid() | Text | Generates a new UUID v4. |
urlencode | urlencode(text) | Text | Percent-encodes for use in URLs. |
urldecode | urldecode(text) | Text | Decodes percent-encoded text. |
5.9 Statistics Functions (13)
| Function | Syntax | Returns | Description |
|---|---|---|---|
mean | mean(n1, n2, ...) | Number | Arithmetic mean. |
median | median(n1, n2, ...) | Number | Middle value (or average of two middles). |
mode | mode(n1, n2, ...) | Number | Most-frequent value. |
variance | variance(n1, n2, ...) | Number | Sample variance. |
varp | varp(n1, n2, ...) | Number | Population variance. |
stdev | stdev(n1, n2, ...) | Number | Sample standard deviation. |
stdevp | stdevp(n1, n2, ...) | Number | Population standard deviation. |
range | range(n1, n2, ...) | Number | max - min. |
percentile | percentile(array, p) | Number | p-th percentile (0–100). |
quartile | quartile(array, q) | Number | q-th quartile (0–4). |
zscore | zscore(value, array) | Number | Z-score of value relative to the distribution. |
correl | correl(arrayX, arrayY) | Number | Pearson correlation coefficient. |
covar | covar(arrayX, arrayY) | Number | Covariance. |
5.10 Advanced Math & Calculus (20)
| Function | Syntax | Returns | Description |
|---|---|---|---|
ln | ln(x) | Number | Natural logarithm. |
log | log(x, base?) | Number | Logarithm with optional base (default 10). |
log2 | log2(x) | Number | Base-2 logarithm. |
pow | pow(base, exponent) | Number | Exponentiation. |
atan2 | atan2(y, x) | Number | Two-argument arctangent. |
hypot | hypot(x, y) | Number | Hypotenuse (sqrt(x² + y²)). |
factorial | factorial(n) | Number | n! |
combinations | combinations(n, k) | Number | Binomial coefficient C(n,k). |
permutations | permutations(n, k) | Number | P(n,k). |
gcd | gcd(a, b) | Number | Greatest common divisor. |
lcm | lcm(a, b) | Number | Least common multiple. |
pi | pi() | Number | π. |
e | e() | Number | Euler's number. |
derivativeat | derivativeat(expr, var, x) | Number | Numerical derivative of expr in var at x. |
integral | integral(expr, var, a, b) | Number | Definite integral on [a, b]. |
integralbetween | integralbetween(arrayX, arrayY) | Number | Trapezoidal integration of paired samples. |
simpson | simpson(expr, var, a, b, n) | Number | Simpson's-rule integration with n subintervals. |
slope | slope(arrayX, arrayY) | Number | Slope of the linear regression line. |
intercept | intercept(arrayX, arrayY) | Number | Y-intercept of the linear regression line. |
5.11 System / Get() Functions (4)
| Function | Syntax | Returns | Description |
|---|---|---|---|
get | get(key) | Varies | Reads a runtime system value by key (e.g. "currentUser", "projectName", "sessionID"). |
get_lastdialogbutton | get_lastdialogbutton() | Integer | Index (1, 2, 3) of the button the user clicked in the most recent dialog. |
get_lastdialogbuttonlabel | get_lastdialogbuttonlabel() | Text | Label text of the button the user clicked in the most recent dialog. |
5.12 Array Functions (19)
| Function | Syntax | Returns | Description |
|---|---|---|---|
array | array(v1, v2, ...) | Array | Constructs an array. Nestable for matrices: array(array(1,2), array(3,4)). |
arraylength | arraylength(a) | Integer | Element count. |
arrayget | arrayget(a, i, j?) | Varies | Element at index i (0-based), optional j for nested arrays. |
arrayset | arrayset(a, i, value) | Array | Returns a with index i replaced. |
arraypush | arraypush(a, value) | Array | Appends value. |
arraypop | arraypop(a) | Array | Removes the last element. |
arrayconcat | arrayconcat(a, b) | Array | Concatenates two arrays. |
arrayslice | arrayslice(a, start, end?) | Array | Sub-array from start (inclusive) to end (exclusive). |
arrayindexof | arrayindexof(a, value) | Integer | 0-based index, or -1 if not present. |
arraycontains | arraycontains(a, value) | Boolean | True if the array contains value. |
arrayjoin | arrayjoin(a, separator) | Text | Joins elements with separator. |
arraysplit | arraysplit(text, separator) | Array | Splits text into an array. |
arraysort | arraysort(a) | Array | Returns the array sorted ascending. |
arrayreverse | arrayreverse(a) | Array | Returns the array reversed. |
arrayunique | arrayunique(a) | Array | Removes duplicates, preserving order. |
arraysum | arraysum(a) | Number | Sum of numeric elements. |
arraymean | arraymean(a) | Number | Mean of numeric elements. |
arraymin | arraymin(a) | Number | Smallest element. |
arraymax | arraymax(a) | Number | Largest element. |
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.
| Function | Syntax | Returns | Description & 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. |
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.
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:
- Script List (left): All scripts in the project. Click the "+" button to create a new script.
- Step List (center): The ordered sequence of steps in the selected script. Drag steps to reorder. Click "+" to add a step.
- Step Inspector (right): Configure the selected step's parameters, conditions, and options.
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:
- Step Conditions: Any step can have a condition formula. If it evaluates to false, the step is skipped.
- If/Else Branching: Use
setVariableto store a condition result, then use that variable as conditions on subsequent steps. - Looping: Use
goToRecordwith "Next" to iterate through a found set. Combine with step conditions to create loop-exit logic. - Sub-Scripts: Use
performScriptto call another script, optionally passing a parameter.
6.4 Event Triggers
Scripts can be triggered automatically in response to events. Configure triggers in the script's properties:
| Trigger | Fires When |
|---|---|
| On Record Load | A record is loaded into a form. |
| On Record Commit | A record is about to be saved. Returning false cancels the save. |
| On Record Delete | A record is about to be deleted. Returning false cancels the deletion. |
| On Form Open | A form is opened or navigated to. |
| On Form Close | A form is about to close. |
| On Timer | At 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.
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
setVariable |
name (text), value (formula) |
Creates or updates a script-local variable. Reference it later with $name. |
7.6 Dialog & UI Steps
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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
| Step | Parameters | Description |
|---|---|---|
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"). |
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
| Step | Parameters | Description |
|---|---|---|
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.
| Step | Parameters | Description |
|---|---|---|
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. |
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.
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).
| Object | Description |
|---|---|
| Label | Static text or a formula-driven display. |
| Text Field | Single-line editable input bound to a text, email, url, or longText field. |
| Text Area | Multi-line editable input for longText fields. |
| Number Field | Numeric input bound to an integer, decimal, or autoIncrementPrimaryKey field. |
| Checkbox | Toggle bound to a boolean field. |
| Radio Group | Mutually-exclusive choice from a value list (static or table-backed). |
| Dropdown | Compact popup menu bound to a choice or foreignKey field. |
| Date Picker | Calendar picker bound to a date field. |
| Time Picker | Clock picker bound to a time field. |
| Date-Time Picker | Combined date + time picker bound to a dateTime field. |
| Image View | Displays an imageReference field, a static image from project assets, or an SF Symbol (rasterised server-side for the web). |
| Button | Triggers a script when clicked. Same script runs on web buttons via /_action/:scriptID. |
| Separator | Horizontal or vertical line for visual grouping. |
| Group Box | Bordered container that visually corrals other objects under a title. |
| Tab Control | Tabbed container that swaps which child objects are visible per active tab. |
| Portal | Displays related records from a child table in a scrollable list. Inline editing supported. |
| Web View | Embedded web content, useful for rendering HTML from a longText field. |
| Container | Generic layout container (used by templates and by the AI Assistant when composing complex layouts). |
| Chart | Bar, line, area, pie, doughnut, or scatter chart rendered by chart.js (see section 8.6). |
| Dashboard Embed | Embeds an existing dashboard (KPIs, charts, recent records, notes) inside a form. |
| Spacer | Invisible space used to push other objects apart during auto-layout. |
| Rectangle | Decorative shape with configurable fill and border. |
| Rounded Rectangle | Decorative shape with a corner radius. |
| Ellipse | Decorative oval / circle. |
| Line | Decorative 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.
| Band | Purpose | Pinnable |
|---|---|---|
| Master Header | Shared header that appears above every layout in the project (logo, global navigation). | Yes |
| Header | This form's header. Page title, action buttons, filter chips. | Yes |
| Body | The primary content area. First-class draggable band — drop fields, portals, charts here. | No |
| Navigation | Inline navigation strip between body and footer (typically pagination or step indicators). | Yes |
| Footer | This form's footer. Save / Cancel buttons, status text. | Yes |
| Master Footer | Shared 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:
- Alignment: Align selected objects by their left, right, top, bottom, or center edges.
- Distribution: Space objects evenly horizontally or vertically.
- Snap to Grid: Toggle grid snapping for precise placement (grid size configurable in Settings).
- Tab Order: Set the order in which fields receive focus when the user presses Tab.
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:
- Drag a Portal object onto the canvas.
- In the Object Inspector, select the relationship to follow.
- Choose which fields from the related table to display as columns.
- 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:
- Formula — a boolean expression evaluated against the current record (e.g.
balance < 0,DueDate < today(),status = "Overdue"). - Attribute overrides — background color, foreground (text) color, border color, and a bold toggle. Any attribute left blank uses the object's static value.
- Enabled toggle — flip a rule off without deleting it.
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
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:
- Bar — classic vertical bars. Supports stacking when you have multiple Y fields.
- Line — smoothed connecting lines, one per Y field.
- Area — line chart with filled regions under each series.
- Pie / Doughnut — one slice per X-axis label using the first Y field as slice values.
- Scatter — point plot, useful when X is numeric.
Placing a chart:
- Drag the Chart object from the object palette onto the canvas.
- Open the Object Inspector. The Chart panel auto-selects the form's own table and offers sensible X-axis defaults.
- 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.
- Adjust the chart type, title, aggregation (Sum, Average, Count, Min, Max, None), and per-series colors as needed.
Chart configuration fields:
| Setting | Meaning |
|---|---|
| Type | Bar, Line, Area, Pie, Doughnut, or Scatter. |
| Title | Optional title displayed above the chart. |
| Table | Source table. Defaults to the form's own table; pick any table in the project. |
| X Field | Categorical or date field used for the x-axis (or pie/doughnut slice labels). |
| Y Fields | Numeric fields (integer, decimal, or calculation). Each becomes a series; pie/doughnut charts use only the first. |
| Aggregate | How to combine Y values for rows that share an X value: Sum, Average, Count, Min, Max, or None. |
| Limit | Maximum number of source rows to read (default 100). |
| Colors | Per-series color picker. Blank slots fall back to the default 8-color palette. |
| Show legend | Hide or show the chart.js legend. |
| Stacked | Stack 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.
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:
- Resize columns by dragging their header borders.
- Reorder columns by dragging column headers.
- Click a cell to edit its value inline.
- Double-click a row to open the Record Inspector (a detail panel showing all fields).
- Right-click a row for context menu actions (duplicate, delete, copy, etc.).
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.
9.2 Searching
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.
| Operator | Example | Description |
|---|---|---|
= | =Smith | Exact match. |
== | ==smith | Exact match, case-sensitive. |
* | Sm* | Wildcard (zero or more characters). |
> < >= <= | >100 | Numeric or date comparison. |
.. | 100..500 | Range (inclusive). |
! | !Smith | Not 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
- New Record: Click the "+" button in the toolbar or press Cmd+N.
- Duplicate Record: Select a record and press Cmd+D.
- Delete Record: Select a record and press Cmd+Delete. Confirm in the dialog.
- Commit: Press Return or click outside the row. Validation runs automatically.
- Revert: Press Escape before committing to discard changes.
Reports
The Reports workspace turns table data into printable, shareable documents. Open it from the sidebar's Reports entry.
- Build a report by selecting a source table, choosing columns, and adding optional group-by + summary rows.
- Filter with the same query language as the Data Browser (operators like
=,>,..,!). - Preview renders the report inline before printing.
- Print or export to PDF via the standard macOS print dialog.
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
- KPI tiles — a single number (count, sum, average, min, max) from any table, with optional row filter and click-through formula. The driving number is computed at SQL level, not in Swift.
- Charts — bar, line, area, pie, doughnut, scatter. Same chart engine as form-embedded charts.
- Recent Records — a compact list of the N most-recently-touched rows from a table. Useful for "latest leads", "open tickets", "last 10 jobs". Each row is clickable and opens the table's default form on that record.
- Note — rendered markdown / rich text. Useful as a section header, an instructions panel, or a "what to look at today" callout.
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
- Startup dashboard: a project can be configured to open directly on a dashboard. All five bundled templates ship with one to demonstrate the pattern.
- Embed inside a form: drop a Dashboard Embed object onto any form to show the dashboard inline (handy for "overview at the top of the home form").
- Background: dashboards take the same background colour / image controls as forms.
- Navigate from a script: the
goToDashboardscript step jumps to a named dashboard, in either the native runtime or via a 302 redirect when triggered from a published web button.
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:
- Select the target table (or create a new one from the CSV headers).
- Map CSV columns to table fields.
- Preview the first 100 rows before importing.
- 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:
- Tables — all tables in the file, by their real names (not placeholders).
- Fields — name and type code for each column, mapped to the closest Structa type (text, number, date, time, timestamp, container).
- Records — cell values for every record, with XOR/SCSU decoding and UTF-8/Latin-1 fallback.
- Relationships — joined field pairs between tables. Cardinality is not stored in the binary, so every relationship imports as one-to-many; adjust in the Relationship Designer after import.
What the binary importer cannot recover, because FileMaker does not encode it in a reversible way:
- Layouts (screens / forms)
- Scripts and script triggers
- Value lists
- Access privileges / privilege sets
- Calculated fields' original formulas (the stored values are imported, but the formula text is not)
10.2.2 DDR XML import (recommended for a full migration)
For a complete migration, use FileMaker's Database Design Report export:
- In FileMaker Pro, open your file and choose Tools → Database Design Report…
- Select XML as the output format.
- Import the generated
.xmlfile 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.
.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:
- Scheduled CSV export to a specified directory.
- Batch import of multiple CSV files with automatic table creation.
- Export to JSON for integration with external systems.
- Field content export for extracting images and documents from the assets directory.
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).
11.1 Per-Project Sharing Toggles
Each project carries its own sharing state. Open Sharing › Settings to flip:
- Web sharing enabled — serve this project's forms to browsers on the network. Requires Web Sharing IAP.
- Native sharing enabled — serve this project to other Macs running Structa. Requires a Native Hosting CAL pack.
- Listed in publishing root — when on, the project appears as a tile on the daemon's home page (
/); when off, the project is still reachable at its slug but doesn't show up on the lobby. - Auto-save — when on (the default), field edits in a web form commit on blur and on tab close (via
navigator.sendBeacon); when off, the user must explicitly press Save. - Native Guest / Web Guest — per-surface toggles next to each share URL. When on, that surface lets visitors in without an account; when off (default with accounts defined), Structa challenges for credentials.
- Guest can: Read / Create / Edit / Delete — four global CRUD flags that gate what an anonymous visitor may do. Visible only when at least one Guest toggle is on. Defaults: read on, mutations off.
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:
| URL | Serves |
|---|---|
/ | Daemon lobby — lists every project where "Listed in publishing root" is on. |
/jobs | Project root — redirects to the project's default form (set in Sharing Settings) or shows a per-project list of forms. |
/jobs/active-jobs | HTML list view of the form's table. |
/jobs/active-jobs/:id | HTML detail view for one record. List forms with a default form configured redirect here from the list view. |
/jobs/active-jobs/_action/:scriptID | POST 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/_lock | POST / 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:
- Go to Form → the target form's profile URL.
- Go to Table → that table's first published list form.
- Go to Record →
/<project>/<form>/<rowid>. - Go to Dashboard → the dashboard route under the same project.
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:
| Mode | Description |
|---|---|
| None | No authentication. All routes are public (best for read-only catalogue or localhost-only). |
| Basic Auth | HTTP Basic Authentication. Users are defined in the publish profile with a username and password. |
| Session Auth | Cookie-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:
| Style | Vibe | Best for |
|---|---|---|
| Classic | Clean grid of tiles with a header strip. | Internal team page, mixed projects. |
| Marquee | Full-bleed hero with marching tile carousel. | Single flagship project, marketing-feel landing. |
| Terminal | Monospaced, dark, command-line aesthetic. | Internal tooling, dev / data-engineering shops. |
| Boardwalk | Wide horizontal card row, soft pastel palette. | Customer-facing catalogue. |
| Reception | Centred 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:
- 25 form-object types — each has a real HTML counterpart, not a generic fallback.
- SF Symbols — rasterised server-side via AppKit, encoded as PNG data URLs, tinted to match the native appearance. Browsers see the actual glyph.
- Embedded images — container-field images stream from
/_assets/…; image objects with bundled appearance data resolve to data URLs. - Conditional formatting — evaluated server-side per record; CSS-inline styles applied.
- Form-level backgrounds — colour and image both honoured (image streams from the project's appearance store).
- Charts — chart.js renders with the same data the native form is showing, in a transparent wrapper.
- Section bands — master-header, header, body, navigation, footer, master-footer all preserved.
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:
- URL — the HTTPS endpoint to call (HTTP also accepted for localhost development).
- Events — any combination of
insert,update,delete. - Table ID — optional. When set, the webhook only fires for that table; when
nil, it fires for every table (firehose). - Headers — optional key/value pairs sent on every request (e.g.
Authorization: Bearer …for Slack, Zapier, or a custom auth gateway). - Enabled — flip a webhook off without deleting it.
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:
- Page chrome — the surrounding header, list tables, buttons, and inputs reflow at two breakpoints (≤768 px tablet, ≤600 px phone) for proper touch targets (44 px minimum), 16 px font sizes so iOS doesn't zoom on focus, and a "card stack" layout where each list row becomes a full-width tile with field-name labels on the left.
- CSS auto-reflow — when a designed form-canvas is loaded on a narrow viewport but the device wasn't recognised as a phone (e.g. a desktop browser resized down), the abs-positioned canvas is overridden in CSS to flow vertically. Decorative shapes (lines, rectangles, spacers) are hidden so they don't clutter the stack.
- Server-side device detection — phones get a completely separate stacked render path. Each field is emitted as a clean
<label>Field Name</label><input/>pair regardless of where the designer placed the corresponding controls on the desktop canvas. This guarantees labels and inputs stay paired even when the desktop layout put them in widely-separated rows. Action buttons follow the field stack so scripts can still be invoked.
Detection precedence:
?layout=phone | tablet | desktopin the URL — explicit override. Handy for QA on a desktop browser.Sec-CH-UA-Mobile: ?1client hint — modern browsers.User-Agentheuristic (matches "iPhone", "Android", "Mobi", "Windows Phone"; iPad is detected separately as a tablet).- Default: desktop.
?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
- Open your project and switch to Host mode (View > Mode > Host, or the toolbar toggle).
- Select a publish profile (or create one if none exists).
- Click Start Server.
- Structa displays the local URL (e.g.,
http://192.168.1.100:8080) that other devices can use to connect.
12.2 The Dashboard
The Host Mode dashboard shows real-time information:
- Server status (running/stopped), port, and uptime.
- Connected clients with IP addresses and last activity timestamps.
- Request log showing recent HTTP requests (method, path, status code, response time).
- Performance metrics (requests per minute, average response time, active sessions).
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.
12.4 HTTPS
Structa supports HTTPS with TLS certificates for encrypted connections. To enable HTTPS:
- In the publish profile, toggle HTTPS on.
- Provide a certificate file (.pem) and private key file (.pem), or let Structa generate a self-signed certificate for development/testing.
- 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:
- Presence — when a client opens a form, the daemon broadcasts a
{"presence":"joined", …}message; on close,{"presence":"left", …}. Every other client sees who else is on the same form / record and renders a presence chip. - Advisory locks — when a user starts editing, the client POSTs
/_lock. The daemon either grants the lock (broadcasting{"lock":"acquired", …}) or refuses with the holder's identity. Locks are advisory: the daemon won't reject a write to a locked row, but other clients can show a padlock glyph and disable their edit affordances. - sendBeacon release — when a browser tab closes (or the user navigates away), the page calls
navigator.sendBeaconto POST/_lock/release. sendBeacon is the only browser API that reliably fires during unload, so this prevents zombie locks from closed tabs. - TTL pruning — even without an explicit release, locks expire on a configurable timeout (default 120 seconds of inactivity). The lock service auto-prunes every minute.
13.3 Native Lock & Skip-Locked
The native runtime exposes locks to scripting:
- Lock Record / Unlock Record script steps for explicit coordination.
- Skip-locked queries — pass
?skip_locked=1on a JSON APIGET /api/v1/<table>and the daemon returns only rows that no other session holds. Pair this with Lock Record in a Loop to build a job queue where each worker automatically claims a disjoint batch. - Lock indicator banner on detail forms; padlock glyph on list-view rows held by another session.
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:
- Each record has a version counter that increments on every save.
- When a user submits changes, Structa checks that the record version matches what the user loaded.
- 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.
- 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:
- Users — add, edit, deactivate, and delete accounts. Each user has a username, a PBKDF2-SHA256 hashed password (100,000 iterations, 16-byte salt), an optional API token for headless integrations, and a privilege set assignment.
- Privilege Sets — manage role definitions. Three sets are seeded automatically the first time you open a project: Admin (full read/write/create/delete + API), Read-Write (CRUD without admin), and Read-Only (browse only). You can add your own custom sets alongside.
14.2 Privilege Sets
A privilege set bundles four kinds of permission:
- Per-table CRUD — read / create / edit / delete flags per table, stored as JSON so new tables default to allow.
- Visible fields — per-table list of field UUIDs the user is allowed to see; nil = all.
- Editable fields — per-table list of field UUIDs the user can edit; nil = all visible fields are also editable.
- Row filter — an optional formula like
Owner = Get(AccountName)that scopes which rows the user sees on any table. - Caps —
canUseAPIandcanUseFormUIgate the JSON REST API and the form runtime respectively.
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:
- Native Guest — when on, native clients open the project without an account.
- Web Guest — when on, browser visitors skip the sign-in screen.
- Guest can: Read / Create / Edit / Delete — four checkboxes that gate what an anonymous visitor may do. They appear only when at least one Guest toggle is on. Defaults: read on, mutations off.
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.
- Auto-lock — configurable idle timeout and Mac sleep both trigger re-encryption and close.
- Crash recovery — if a plaintext file is found alongside the
.encarchive, Structa keeps the newer copy and flags the project for re-encryption on next close. - Recovery Key — File > Encryption > Export Recovery Key… wraps the project’s symmetric key with a PBKDF2-SHA256 (200k iterations) derived KEK from a user passphrase. Import the same key on another Mac to recover access without the original Keychain.
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.
| Template | Tables | What it’s for |
|---|---|---|
| Contact Us | Submissions | Public-facing contact form for a website. |
| Newsletter Signup | Subscribers | Email list capture with double-opt-in fields. |
| Support Ticket | Tickets | Inbound IT/customer support intake. |
| Bug Report | Reports | Structured bug intake with severity, steps to reproduce, environment. |
| Customer Complaint | Complaints | Formal complaint capture for service businesses. |
| Customer Feedback | Feedback | NPS-style satisfaction survey. |
| Liability Waiver | Waivers | Signed-waiver capture for events and activity providers. |
| Job Application | Candidates, Experience, Education | Relational employment application with work history. |
| Volunteer Application | Volunteers | Charity / event volunteer intake with availability. |
| Vendor Registration | Vendors | Supplier onboarding with W-9 / banking fields. |
| Leave Request | Requests | Employee time-off request with approval workflow. |
| IT Support Request | Requests | Internal IT help desk intake. |
| Appointment Booking | Appointments | Self-serve appointment scheduler. |
| Patient Intake | Patients, History, Medications | Healthcare new-patient onboarding. |
| Site Inspection | Inspections, Findings | Field inspector checklist with photo attachments. |
| Event Registration | Events, Sessions, Attendees, Registrations | 4-table relational event signup. |
| Product Order | Customers, Products, Orders, Line Items | 4-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:
- Open the project you want to use as a template.
- Go to File > Export Template...
- Choose whether to include sample data or export schema only.
- Enter a template name, description, and icon.
- 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
| Feature | Included Free |
|---|---|
| Database Builder (Tables, Fields, Relationships) | Yes |
| Calculation Engine (118 functions) | Yes |
| Script Engine (52 steps) | Yes |
| Form Designer + Runtime | Yes |
| Reports & Dashboards | Yes |
| Data Browser | Yes |
| FileMaker .fmp12 Import | Yes |
| SQL Dump & CSV Import/Export | Yes |
| 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:
- Native CALs — client-access licences for other Structa users connecting to your project from their own Macs over the LAN. Six pack sizes (1, 3, 5, 10, 15, 20 seats). Pick one pack at a time; switching pack sizes is an in-place upgrade/downgrade handled by StoreKit with automatic proration.
- Web Unlimited — unlimited browser-based access on the host's network. Independent of the Native CAL subscription; you can subscribe to either, both, or neither.
| Subscription | Seats | Surface |
|---|---|---|
| 1-User Pack | 1 additional seat | Native (LAN) |
| 3-User Pack | 3 additional seats | Native (LAN) |
| 5-User Pack | 5 additional seats | Native (LAN) |
| 10-User Pack | 10 additional seats | Native (LAN) |
| 15-User Pack | 15 additional seats | Native (LAN) |
| 20-User Pack | 20 additional seats | Native (LAN) |
| Web Unlimited | Unlimited browsers | Web (LAN) |
17. Keyboard Shortcuts
File & Project
| Action | Shortcut |
|---|---|
| New Project | Cmd+N |
| Open Project | Cmd+O |
| Close Project | Cmd+Shift+W |
| Save | Cmd+S |
| Import Database… | Cmd+Shift+I |
| Apply Template… | Cmd+Shift+T |
| Manage Security… | Cmd+Shift+K |
| Manage Value Lists… | Cmd+Shift+V |
| Undo | Cmd+Z |
| Redo | Cmd+Shift+Z |
| Preferences | Cmd+, |
Mode Switching
| Action | Shortcut |
|---|---|
| Design Mode | Cmd+1 |
| Runtime Mode | Cmd+2 |
| Host Mode | Cmd+3 |
Design Mode Sub-Routes
| Action | Shortcut |
|---|---|
| Tables | Cmd+Ctrl+T |
| Relationships | Cmd+Ctrl+E |
| Forms | Cmd+Ctrl+F |
| Scripts | Cmd+Ctrl+S |
Other Navigation
| Action | Shortcut |
|---|---|
| Raw Data (Runtime) | Cmd+Shift+R |
| Publish Project | Cmd+Shift+P |
| Keyboard Shortcuts (this sheet) | Cmd+/ |
Data Browser & Runtime Form
| Action | Shortcut |
|---|---|
| New Record | Cmd+N |
| Duplicate Record | Cmd+D |
| Delete Record | Cmd+Delete |
| Find / Search | Cmd+F |
| Show All Records | Cmd+Shift+F |
| Sort Records | Cmd+Shift+S |
| Next Record | Ctrl+Down |
| Previous Record | Ctrl+Up |
| Print Form (current record, detail view) | Cmd+P |
| Commit Record | Return |
| Revert Record | Escape |
Documentation Window
| Action | Shortcut |
|---|---|
| Find in Docs (filter + highlight matches) | Cmd+F |
| Clear search / restore full view | Esc |
Form Designer
| Action | Shortcut |
|---|---|
| Select All Objects | Cmd+A |
| Group Objects | Cmd+G |
| Ungroup Objects | Cmd+Shift+G |
| Bring to Front | Cmd+Shift+] |
| Send to Back | Cmd+Shift+[ |
| Toggle Snap to Grid | Cmd+' |
| Preview Form | Cmd+Shift+P |
Script Editor
| Action | Shortcut |
|---|---|
| Run Script | Cmd+R |
| Add Step | Cmd+Shift+N |
| Delete Step | Cmd+Delete |
| Move Step Up | Cmd+Option+Up |
| Move Step Down | Cmd+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
.structapackage 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
.fp7files 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()orisempty()to handle null values gracefully. - Related field not found: Verify the relationship exists and the field name in the
TableName::fieldNamereference 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:
- Open Console.app (in Applications > Utilities).
- In the sidebar, navigate to Crash Reports.
- Look for entries named "Structa" with the date/time of the crash.
- 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:
- The Structa version (from Structa > About Structa).
- Your macOS version.
- A description of what you were doing when the crash occurred.
- The project file (if possible), or a description of its schema.
18.3 Contact the Developer
For suggestions, bug reports, or complaints, choose Help > Contact the Developer… from the menu bar. A panel opens with:
- Category picker — Suggestion, Bug Report, Complaint, or Other. The chosen category is prefixed onto the email subject line for easy triage.
- Subject — optional one-line summary.
- Message — your note. Structa automatically appends the app version and macOS version when it sends the email.
- Image attachments — click Add Image… to attach screenshots or photos. Up to 10 images, each 5 MB maximum. Click the × on a thumbnail to remove one.
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.
Structa v2.2 — Documentation © 2026 FI Forensic Intelligence, Ltd. All rights reserved.