Data model — Finder
The central configurable resource — each Finder represents one embeddable widget instance that a visitor interacts with on an external site.
Table: finders
| Column | Type | Nullable | Description |
|---|---|---|---|
| id | bigint (auto-increment) | no | Primary key |
| title | varchar | no | Human-readable name shown in the dashboard |
| description | text | yes | Optional freetext description |
| token | varchar (unique) | no | Public finder key consumed by the widget embed script; used as the path param in /ext/finders/details/{token} |
| settings | json | yes | Runtime behavior config (radius, unit type, search mode, refinement toggles). See JSON columns |
| design | json | yes | Full design-token config (colors, fonts, border radii, layout, map, listings, localization). See JSON columns |
| authorized_urls | json | yes | Array of allowed origin URLs. The backend checks Origin/Referer against this list for widget requests |
| advanced_refinements | json | yes | Array of visitor-driven filter/sort rule sets. See JSON columns |
| created_at | timestamp | no | Laravel auto-managed |
| updated_at | timestamp | no | Laravel auto-managed |
⚠️ Warning:
tokenis the public identifier used by the widget — it is not the same asid. The token is set once on creation and is the key embedded in customer websites. Rotation requires updating all embed scripts. 🔴 [NEEDS CLARIFICATION: Is there a token-rotation flow? What does the dashboard offer the user if they need to rotate a compromised token?]
Relations
- belongsToMany
Map— viafinder_mappivot table. In practice each Finder has exactly one backing Map (created automatically on Finder creation — see Paired Map creation). - belongsToMany
User— viafinder_userpivot. These are the owning users of the Finder. - belongsToMany
Location— viafinder_locationpivot. Direct location associations (separate from Map-sourced locations). - belongsToMany
Workspace— viaworkspace_finderpivot. Scopes the Finder to one or more workspaces. - hasMany
FinderIntegration— BYOK autocomplete provider keys stored per-finder. Seecodebases/backend/autocomplete-providers.
Paired Map creation
When a Finder is created, the application calls Finder::ensureBackingMap() to guarantee a single backing Map exists. The method (in app/Models/Finder.php) runs inside a DB transaction:
- If the Finder has no associated Maps, a new
Mapis created with the sametitleas the Finder. - The new Map inherits the Finder’s owners and workspaces via
syncWithoutDetaching. - The Map is linked to the Finder via the
finder_mappivot. - If multiple Maps exist (data-integrity edge case),
ensureBackingMapmerges their sets and locations into the lowest-id Map and removes the extras.
The backing Map’s description is set to "Internal backing map for finder scope." and is not customer-editable.
See also: Map data model for the Map side of this relationship.
JSON columns
settings
Controls widget runtime behavior.
{
radius: number; // Default search radius value
unitType: 'mi' | 'km';
showAllListings: boolean;
searchMode?: 'default' | 'locations_only' | 'natural_language';
}design
Full design-token payload delivered to the widget. Nested structure:
| Top-level key | Description |
|---|---|
refinements | Which refinement controls are active (tag, countries, sort, radius, distance) |
design.map | Map provider (tomtom|mapbox|here|osm), style variant, API key, control visibility, marker color |
design.app | Layout string, search position |
design.localization | Locale (en|es), override config JSON string |
design.listings | Which address/distance/tag fields to show, tag display options |
design.borderRadius | Per-element radius values (button, input, dropdown, map, tag, location card) |
design.colors | Full palette — background, button variants, location card, tag, input, border |
design.fonts | Body/heading/button/tag font families + base/heading/button/tag font sizes |
design.customCSS | Raw CSS string injected into the widget iframe |
design.refinements | Refinement panel and filter UI options (radius range, popup, quick-filter row, filter/sort styles and placement) |
⚠️ Warning: The
designcolumn contains the full widget config. Changes require a publish step (POST /ext/finders/details/{token}/sync) to propagate to the CDN. Editingdesigndirectly in the DB without re-publishing will leave the CDN stale. Seearchitecture/data-flow-builder.
authorized_urls
Array of objects with a single url key:
[
{ "url": "https://example.com" },
{ "url": "https://staging.example.com" }
]The backend validates the Origin or Referer header of widget requests against this list.
🔴 [NEEDS CLARIFICATION: Does the system support wildcard subdomains (e.g. *.example.com) in authorized URLs?]
advanced_refinements
Array of named refinement rule sets, each with a list of values:
[
{
"name": "Open on Weekends",
"values": [{ "value": "saturday" }, { "value": "sunday" }]
}
]See the customer guide at customization/advanced-refinements for the full rule schema.
Computed / virtual attributes
None declared via $appends. The owners relation returns only users.id (not full User objects) as a deliberate performance optimization.
Cross-links
- Map data model — the paired backing Map
- API reference — Finders
codebases/backend/autocomplete-providers— FinderIntegration BYOK keysarchitecture/data-flow-builder— publish pipeline