These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

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

ColumnTypeNullableDescription
idbigint (auto-increment)noPrimary key
titlevarcharnoHuman-readable name shown in the dashboard
descriptiontextyesOptional freetext description
tokenvarchar (unique)noPublic finder key consumed by the widget embed script; used as the path param in /ext/finders/details/{token}
settingsjsonyesRuntime behavior config (radius, unit type, search mode, refinement toggles). See JSON columns
designjsonyesFull design-token config (colors, fonts, border radii, layout, map, listings, localization). See JSON columns
authorized_urlsjsonyesArray of allowed origin URLs. The backend checks Origin/Referer against this list for widget requests
advanced_refinementsjsonyesArray of visitor-driven filter/sort rule sets. See JSON columns
created_attimestampnoLaravel auto-managed
updated_attimestampnoLaravel auto-managed

⚠️ Warning: token is the public identifier used by the widget — it is not the same as id. 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 — via finder_map pivot table. In practice each Finder has exactly one backing Map (created automatically on Finder creation — see Paired Map creation).
  • belongsToMany User — via finder_user pivot. These are the owning users of the Finder.
  • belongsToMany Location — via finder_location pivot. Direct location associations (separate from Map-sourced locations).
  • belongsToMany Workspace — via workspace_finder pivot. Scopes the Finder to one or more workspaces.
  • hasMany FinderIntegration — BYOK autocomplete provider keys stored per-finder. See codebases/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:

  1. If the Finder has no associated Maps, a new Map is created with the same title as the Finder.
  2. The new Map inherits the Finder’s owners and workspaces via syncWithoutDetaching.
  3. The Map is linked to the Finder via the finder_map pivot.
  4. If multiple Maps exist (data-integrity edge case), ensureBackingMap merges 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 keyDescription
refinementsWhich refinement controls are active (tag, countries, sort, radius, distance)
design.mapMap provider (tomtom|mapbox|here|osm), style variant, API key, control visibility, marker color
design.appLayout string, search position
design.localizationLocale (en|es), override config JSON string
design.listingsWhich address/distance/tag fields to show, tag display options
design.borderRadiusPer-element radius values (button, input, dropdown, map, tag, location card)
design.colorsFull palette — background, button variants, location card, tag, input, border
design.fontsBody/heading/button/tag font families + base/heading/button/tag font sizes
design.customCSSRaw CSS string injected into the widget iframe
design.refinementsRefinement panel and filter UI options (radius range, popup, quick-filter row, filter/sort styles and placement)

⚠️ Warning: The design column contains the full widget config. Changes require a publish step (POST /ext/finders/details/{token}/sync) to propagate to the CDN. Editing design directly in the DB without re-publishing will leave the CDN stale. See architecture/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.