> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inploi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Job Search Plugin

> Full-featured job search widget

# Job Search Plugin

The Job Search plugin renders a complete job search interface with filters, search, and job cards.

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install @inploi/plugin-job-search
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add @inploi/plugin-job-search
    ```
  </Tab>

  <Tab title="CDN">
    ```html theme={null}
    <script defer src="https://sdk.inploi.com/@inploi/sdk/cdn/index.js"></script>
    <script defer src="https://sdk.inploi.com/@inploi/plugin-job-search/cdn/index.js"></script>
    ```
  </Tab>
</Tabs>

## Basic usage

<Tabs>
  <Tab title="Package">
    ```typescript theme={null}
    import { initialiseSdk } from '@inploi/sdk';
    import { jobSearchPlugin } from '@inploi/plugin-job-search';

    const sdk = initialiseSdk({ publishableKey: 'pk_...', env: 'sandbox' });
    const jobSearch = sdk.register(jobSearchPlugin());

    jobSearch.render({
      widgetId: 'main',
      properties: [
        { key: 'city', label: 'City', select: 'many' },
        { key: 'contract_type', label: 'Contract', select: 'one' },
      ],
      jobCard: {
        subheading: { key: 'company_name' },
        infoTags: [{ key: 'city' }],
      },
      theme: {
        mode: 'light',
        corners: 'soft',
        highlights: 'fill',
        accent: { hue: 260, chroma: 1 },
        typography: {},
      },
      initialState: { filters: {}, query: '', mode: 'query', page: 1, view: 'list' },
      onStateChange: (state) => console.log('State changed', state),
    });
    ```
  </Tab>

  <Tab title="CDN">
    ```html theme={null}
    <script defer src="https://sdk.inploi.com/@inploi/sdk/cdn/index.js"></script>
    <script defer src="https://sdk.inploi.com/@inploi/plugin-job-search/cdn/index.js"></script>
    <div data-widget="inploi-job-search" data-widget-id="main"></div>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        const sdk = inploi.initialiseSdk({ publishableKey: 'pk_...', env: 'sandbox' });
        const jobSearch = sdk.register(inploi.jobSearchPlugin());
        jobSearch.render({
          widgetId: 'main',
          properties: [{ key: 'city', label: 'City', select: 'many' }],
          jobCard: { subheading: { key: 'company_name' }, infoTags: [{ key: 'city' }] },
          theme: { mode: 'light', corners: 'soft', highlights: 'fill', accent: { hue: 260, chroma: 1 }, typography: {} },
          initialState: { filters: {}, query: '', mode: 'query', page: 1, view: 'list' },
          onStateChange: function(state) { console.log('State changed', state); },
        });
      });
    </script>
    ```
  </Tab>
</Tabs>

## Host element

Add a container element where the widget will render:

```html theme={null}
<div data-widget="inploi-job-search" data-widget-id="main"></div>
```

<Note>
  Use `data-widget-id` to identify the container when rendering multiple widgets on the same page.
</Note>

## Configuration

### Theme

| Option          | Type                                                | Description                                                                                                                                           |
| --------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`          | `light`, `dark`                                     | Color scheme                                                                                                                                          |
| `corners`       | `sharp`, `soft`, `rounded`                          | Border radius style                                                                                                                                   |
| `highlights`    | `fill`, `stroke`                                    | Button/tag style                                                                                                                                      |
| `accent`        | `{ hue: number, chroma: number }`                   | Primary accent color. `hue`: 0–360 color angle, `chroma`: 0–2 intensity multiplier                                                                    |
| `neutral`       | `{ hue: number, chroma: number }`                   | Optional neutral base color (same format as `accent`)                                                                                                 |
| `monetary`      | `{ hue: number, chroma: number }`                   | Optional color for salary/monetary values                                                                                                             |
| `typography`    | `object`                                            | Font settings: `{ fontFamily: string, weight: number \| string, style: string }`. Supports per-slot overrides: `filter`, `tag`, `input`, `jobHeading` |
| `accessibility` | `{ highContrast: boolean, reducedMotion: boolean }` | Accessibility preferences                                                                                                                             |

### Properties (Filters)

Define which job properties appear as filters:

```typescript theme={null}
properties: [
  { key: 'city', label: 'City', select: 'many' },
  { key: 'contract_type', label: 'Contract', select: 'one', searchable: true },
  { key: 'custom_data.tags', label: 'Category', select: 'one' },
]
```

| Option            | Type                            | Description                                                                                     |
| ----------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `key`             | `string`                        | Job field key or `custom_data.*` path                                                           |
| `label`           | `string`                        | Display label                                                                                   |
| `select`          | `one`, `many`                   | Single or multi-select                                                                          |
| `icon`            | `string`                        | Override the default filter icon                                                                |
| `searchable`      | `boolean`                       | Allow searching within filter options                                                           |
| `hidden`          | `boolean`                       | Hide from UI (useful for hard-coded filters)                                                    |
| `value`           | `string \| string[]`            | Fixed filter value (cannot be changed by user)                                                  |
| `defaultValue`    | `string \| string[]`            | Default value (can be changed by user)                                                          |
| `results`         | `all`, `only-available`         | Whether to show all options or only those with results                                          |
| `sortSuggestions` | `{ by: string, order: string }` | `by`: `results-count`, `alphabetically`, or `numerically`. `order`: `ascending` or `descending` |

### Job Card

Customize how job cards display:

```typescript theme={null}
jobCard: {
  subheading: { key: 'company_name' },
  infoTags: [
    { key: 'city' },
    { key: 'employment_type' },
    { key: 'contract_type' },
  ],
  logo: '2:1',
  postedDate: 'show',
  openJob: 'redirect',
}
```

| Option         | Type                           | Description                                               |
| -------------- | ------------------------------ | --------------------------------------------------------- |
| `subheading`   | `{ key, transform? } \| null`  | Field to show as subheading                               |
| `infoTags`     | `{ key, transform? }[]`        | Fields to show as tags on the card                        |
| `logo`         | `hidden`, `1:1`, `2:1`         | Company logo display                                      |
| `postedDate`   | `show`, `hide`, `only-new`     | Posted date display                                       |
| `newUnderDays` | `number`                       | Days for a job to be considered new (default `7`)         |
| `openJob`      | `redirect`, `redirect-new-tab` | How to open job links                                     |
| `overrides`    | `object`                       | Override salary display, job URL, or click/hover behavior |

### Other options

| Option                  | Type                                                   | Required | Description                                                                                                                                    |
| ----------------------- | ------------------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `initialState`          | `object \| function`                                   | Yes      | Initial state: `{ filters: {}, query: '', mode: 'query', page: 1, view: 'list' }`. Can also be a function receiving config and returning state |
| `widgetId`              | `string`                                               | No       | Identifies the widget when using multiple instances                                                                                            |
| `enableKeywordSearch`   | `boolean`                                              | No       | Enable keyword search input                                                                                                                    |
| `geolocation`           | `{ enabled: boolean, distanceUnit?: 'miles' \| 'km' }` | No       | Enable location-based search. See [Geolocation](#geolocation)                                                                                  |
| `naturalLanguageSearch` | `{ enabled: boolean, suggestions: string[] }`          | No       | Enable AI-powered natural language search with suggested queries                                                                               |
| `i18n`                  | `{ locale?: string, fallbackLocale?: string }`         | No       | Locale settings (e.g. `'en'`, `'fr'`)                                                                                                          |
| `terms`                 | `object`                                               | No       | Override default UI text (\~65 keys covering search labels, filters, errors, dates, geo-search, map, and job types)                            |
| `map`                   | `object`                                               | No       | Enable map view: `{ enabled: boolean, accessToken?: string, style?: string, pinColor?: string, defaultCenter?: [lng, lat] }`                   |
| `feedback`              | `object`                                               | No       | Embed feedback widget (see [Feedback plugin](/sdk/plugins/feedback))                                                                           |
| `alerts`                | `object`                                               | No       | Embed job alerts (see [Job Alerts plugin](/sdk/plugins/job-alerts))                                                                            |

## Geolocation

Geolocation lets candidates search for jobs near a place. When enabled, the search bar gains a **Where** field alongside the keyword input, and results are filtered to jobs within a chosen distance of the candidate's location.

```typescript theme={null}
jobSearch.render({
  // ...rest of configuration
  geolocation: {
    enabled: true,
    distanceUnit: 'miles',
  },
});
```

| Option         | Type              | Default   | Description                                            |
| -------------- | ----------------- | --------- | ------------------------------------------------------ |
| `enabled`      | `boolean`         | —         | Show the **Where** field and enable location filtering |
| `distanceUnit` | `'miles' \| 'km'` | `'miles'` | Unit used for the radius dropdown and distance labels  |

### Setting a location

Candidates can set their location in two ways:

* **Search for a place** — type a town, city, or postcode and pick from the suggestions. Suggestions are biased towards the candidate's approximate location.
* **Use my location** — the **My location** button reads the device's location through the browser. This requires a secure context (HTTPS) and the candidate's permission; if it is denied or unavailable, the candidate is prompted to type a location instead.

### Radius

A **Within** dropdown controls how far from the location to search. The available distances depend on `distanceUnit`:

| `distanceUnit` | Radius options                         | Default  |
| -------------- | -------------------------------------- | -------- |
| `miles`        | 0.25, 0.5, 1, 3, 5, 10, 15, 20, 30, 40 | 20 miles |
| `km`           | 0.5, 1, 3, 5, 10, 15, 25, 50, 100      | 25 km    |

<Note>
  Geolocation is a **filter**, not a re-ordering. It narrows results to jobs within the radius; it does not re-sort matching jobs by distance. The filter only applies once a candidate has chosen a location — enabling the field alone does not change results.
</Note>

### Map view

When the [map](#other-options) is enabled, switching to the map view filters results to the jobs within the area currently shown on the map. Panning or zooming the map updates the results to match the new bounds. Changing the keyword or other filters clears the map-area filter.

### Deep linking

Geolocation state is reflected in the URL using `lat`, `lng`, and `radius` query parameters (the radius is expressed in metres). When you hydrate the widget's `initialState` from the URL, a shared or bookmarked link reopens with the same location search applied.

### Translating the geo labels

All of the geolocation labels — **Where**, **Within**, **My location**, the location search placeholder, and the location permission error messages — can be customised through the [`terms`](#other-options) option.

## Full configuration reference

```typescript theme={null}
type JobSearchPluginParams = {
  widgetId?: string;
  theme: {
    mode: 'light' | 'dark';
    corners: 'rounded' | 'soft' | 'sharp';
    highlights: 'stroke' | 'fill';
    accent: { hue: number; chroma: number };
    neutral?: { hue: number; chroma: number };
    monetary?: { hue: number; chroma: number };
    typography: Partial<FontSettings> & Partial<Record<TextStyle, Partial<FontSettings>>>;
    filtersOffsetTopPx?: number;
    accessibility?: { highContrast: boolean; reducedMotion: boolean };
  };
  properties: FilterProperty[];
  jobCard: {
    subheading: { key: string; transform?: (v: unknown) => string | string[] | null } | null;
    infoTags: { key: string; transform?: (v: unknown) => string | string[] | null }[];
    logo?: 'hidden' | '1:1' | '2:1';
    postedDate?: 'hide' | 'show' | 'only-new';
    newUnderDays?: number;
    openJob?: 'redirect' | 'redirect-new-tab';
    overrides?: {
      salary?: { key: string; transform?: Function };
      baseUrl?: (jobId: string) => string;
      onClick?: (e: MouseEvent) => void;
      onMouseEnter?: (e: MouseEvent) => void;
    };
  };
  initialState: JobSearchState | ((params: Omit<JobSearchPluginParams, 'initialState'>) => JobSearchState);
  enableKeywordSearch?: boolean;
  geolocation?: { enabled: boolean; distanceUnit?: 'miles' | 'km' };
  naturalLanguageSearch?: { enabled: boolean; suggestions: string[] };
  terms?: Partial<JobSearchTranslationTerms>;
  i18n?: { locale?: string; fallbackLocale?: string };
  alerts?: {
    plugin: ReturnType<typeof jobAlertsPlugin>;
    params: { config: { label: string; headerText: string } };
  };
  feedback?: {
    plugin: FeedbackPlugin;
    params?: { terms?: Partial<FeedbackTranslationTerms>; theme: Partial<FeedbackTheme> };
  };
  map?: {
    enabled: boolean;
    accessToken?: string;
    style?: string;
    pinColor?: string;
    defaultCenter?: [number, number];
  };
};

type FontSettings = {
  fontFamily: string;
  weight: 'normal' | 'bold' | 'lighter' | 'bolder' | number;
  style: 'normal' | 'italic';
};

type TextStyle = 'filter' | 'tag' | 'input' | 'jobHeading';

type FilterProperty = {
  key: string;
  label: string;
  select: 'one' | 'many';
  icon?: string;
  searchable?: boolean;
  hidden?: boolean;
  value?: string | string[];
  defaultValue?: string | string[];
  results?: 'all' | 'only-available';
  sortSuggestions?: {
    by: 'results-count' | 'alphabetically' | 'numerically';
    order: 'ascending' | 'descending';
  };
};

type JobSearchState = {
  filters: Record<string, FilterValue | undefined>;
  query: string;
  mode: 'query' | 'vector';
  geo?: GeoSearchFilter;
  page: number;
  view: 'list' | 'map';
};

// Location filter applied to a search. `radius` is always in metres.
type GeoSearchFilter =
  | { type: 'provided'; coords?: { lat: number; lng: number }; radius: number }
  | { type: 'ip'; radius: number }
  | { type: 'bounds'; sw: { lat: number; lng: number }; ne: { lat: number; lng: number } };
```

## API

### `render(params)`

Mounts the job search UI in the host element. Requires an `onStateChange` callback in addition to the configuration above. The callback receives a **partial** state object whenever a slice of the widget state changes (filters, query, page, view, etc.).

```typescript theme={null}
jobSearch.render({
  // ...configuration
  onStateChange: (partial) => console.log(partial),
});
```

Pass `initialData` when hydrating a server-rendered widget (see `prerender` below).

### `prerender(params)`

Returns HTML for server-side rendering. Use `withData: true` to fetch initial results on the server.

```typescript theme={null}
const { html, data } = await jobSearch.prerender({ ...config, withData: true });
```
