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

How to — add a customization option

Adding a new toggle, slider, or picker that appears in the Finder Builder and propagates to the visitor-facing widget. This touches all three repos. The canonical worked example for this pattern is any existing field in V2Config.design.

Step 1 — Extend V2Config in finderBuilderConfig.ts

V2Config is the TypeScript type that represents the full in-memory state of the builder. Add the new field to the correct sub-object:

// dropafinder-app-nextjs/src/components/finders/v2/finderBuilderConfig.ts export type V2Config = { // ... design: { // ... search: { autoclosePanelOnSelect: boolean; // ← add here }; }; // ... };

Then update buildDefaultV2Config() (or equivalent default factory) in the same file to include the new field’s default value:

design: { search: { autoclosePanelOnSelect: true, }, }

💡 Tip: Because design is stored as a JSON column on the finders table, old saved finders won’t have the new key. The default factory value is what the builder falls back to for those finders — choose it to preserve existing behavior.

Step 2 — Add the control to the appropriate Section component

Section components live in dropafinder-app-nextjs/src/components/finders/v2/sections/. Pick the section that semantically owns the new option:

Option categorySection file
Colors, bordersDesignSection.tsx / ThemeSection.tsx
Fonts, sizesTypographySection.tsx
LayoutDesignSection.tsx
Shape / radiusShapeSection.tsx
Map settingsMapsSection.tsx
Search / behaviorBehaviorSection.tsx
RefinementsRefinementsSection.tsx
Content / fieldsContentSection.tsx

Inside the section component, every control calls the update callback, which wraps dispatch({ type: 'SET', payload }) in FinderBuilderV2.tsx. The pattern is a functional updater — you receive the current config and return the next config:

// Inside BehaviorSection.tsx type Props = { config: V2Config; update: (fn: (c: V2Config) => V2Config) => void; }; // Toggle control <Toggle label="Auto-close panel on select" checked={config.design.search.autoclosePanelOnSelect} onChange={(val) => update((c) => ({ ...c, design: { ...c.design, search: { ...c.design.search, autoclosePanelOnSelect: val }, }, })) } />

Never mutate config directly — always spread into a new object tree. The history reducer (historyReducer in FinderBuilderV2.tsx) pushes this into the undo stack via dispatch({ type: 'SET', payload }).

Step 3 — Update the Live Preview payload

The Live Preview iframe receives an updated payload every time config changes. The payload is assembled in previewPayload.ts (dropafinder-app-nextjs/src/components/finders/v2/previewPayload.ts). If your new field maps to a settings.* entry that the widget reads, check that previewPayload.ts includes it in the serialized settings array.

Most design.* fields serialize automatically through the existing settings-serialization loop. Verify by opening the Finder Builder locally, toggling your new option, and confirming the Live Preview iframe reflects the change.

Step 4 — Persist via the API payload transformer

When the user saves, the config is transformed to the wire format by src/lib/api/payloads.ts (toFinderPayload). Ensure your new field is written into the payload:

// dropafinder-app-nextjs/src/lib/api/payloads.ts function serializeDesignSettings(design: V2Config['design']): SettingsEntry[] { return [ // ...existing entries... { name: 'design.search.autoclosePanelOnSelect', value: String(design.search.autoclosePanelOnSelect) }, ]; }

The backend stores this as an entry in the settings JSON array on the finders table — no migration needed.

Step 5 — Read the value in the widget

The widget reads design.* fields from designStore after initializeFinderStores() parses the flat settings array into nested objects. In the relevant Svelte component:

<script> import { designStore } from '../lib/stores.js'; // Always use a default fallback — old finders won't have the key $: autoclosePanelOnSelect = $designStore?.search?.autoclosePanelOnSelect ?? true; </script>

Apply the value in the component’s behavior:

{#if autoclosePanelOnSelect} <!-- close the panel on location select --> {/if}

Step 6 — Backend validation (optional)

If the new value should be validated server-side (e.g., it affects billing or security), add a validation rule to FinderController::update in dropafinder-app-backend/app/Http/Controllers/FinderController.php. For most design/settings values, validation is handled by the TypeScript type system on the frontend — no backend rule is needed.

Step 7 — Verify end-to-end

  1. Open the Finder Builder locally (http://localhost:3000).
  2. Find the new control in the builder UI. Toggle it.
  3. Confirm the Live Preview iframe updates immediately.
  4. Save the finder. Confirm the value persists when you re-open the builder.
  5. Build the widget (npm run build:dev) and open the public embed. Confirm the widget respects the setting.

Step 8 — Deploy in order

  1. Widget deploy first (npm run deploy:02) if the widget reads the new field.
  2. Dashboard deploy second (git push origin main).
  3. Backend deploy only if validation rules were added (make deploy).

See deploy for the full deploy ritual.

Where to next