Skip to main content

Job Search Plugin

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

Installation

npm install @inploi/plugin-job-search

Basic usage

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),
});

Host element

Add a container element where the widget will render:
<div data-widget="inploi-job-search" data-widget-id="main"></div>
Use data-widget-id to identify the container when rendering multiple widgets on the same page.

Configuration

Theme

OptionTypeDescription
modelight, darkColor scheme
cornerssharp, soft, roundedBorder radius style
highlightsfill, strokeButton/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
typographyobjectFont 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:
properties: [
  { key: 'city', label: 'City', select: 'many' },
  { key: 'contract_type', label: 'Contract', select: 'one', searchable: true },
  { key: 'custom_data.tags', label: 'Category', select: 'one' },
]
OptionTypeDescription
keystringJob field key or custom_data.* path
labelstringDisplay label
selectone, manySingle or multi-select
iconstringOverride the default filter icon
searchablebooleanAllow searching within filter options
hiddenbooleanHide from UI (useful for hard-coded filters)
valuestring | string[]Fixed filter value (cannot be changed by user)
defaultValuestring | string[]Default value (can be changed by user)
resultsall, only-availableWhether 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:
jobCard: {
  subheading: { key: 'company_name' },
  infoTags: [
    { key: 'city' },
    { key: 'employment_type' },
    { key: 'contract_type' },
  ],
  logo: '2:1',
  postedDate: 'show',
  openJob: 'redirect',
}
OptionTypeDescription
subheading{ key, transform? } | nullField to show as subheading
infoTags{ key, transform? }[]Fields to show as tags on the card
logohidden, 1:1, 2:1Company logo display
postedDateshow, hide, only-newPosted date display
newUnderDaysnumberDays for a job to be considered new (default 7)
openJobredirect, redirect-new-tabHow to open job links
overridesobjectOverride salary display, job URL, or click/hover behavior

Other options

OptionTypeRequiredDescription
initialStateobject | functionYesInitial state: { filters: {}, query: '', mode: 'query', page: 1, view: 'list' }. Can also be a function receiving config and returning state
widgetIdstringNoIdentifies the widget when using multiple instances
enableKeywordSearchbooleanNoEnable keyword search input
geolocation{ enabled: boolean, distanceUnit?: 'miles' | 'km' }NoEnable location-based search. See Geolocation
naturalLanguageSearch{ enabled: boolean, suggestions: string[] }NoEnable AI-powered natural language search with suggested queries
i18n{ locale?: string, fallbackLocale?: string }NoLocale settings (e.g. 'en', 'fr')
termsobjectNoOverride default UI text (~65 keys covering search labels, filters, errors, dates, geo-search, map, and job types)
mapobjectNoEnable map view: { enabled: boolean, accessToken?: string, style?: string, pinColor?: string, defaultCenter?: [lng, lat] }
feedbackobjectNoEmbed feedback widget (see Feedback plugin)
alertsobjectNoEmbed job alerts (see Job Alerts plugin)

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.
jobSearch.render({
  // ...rest of configuration
  geolocation: {
    enabled: true,
    distanceUnit: 'miles',
  },
});
OptionTypeDefaultDescription
enabledbooleanShow 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:
distanceUnitRadius optionsDefault
miles0.25, 0.5, 1, 3, 5, 10, 15, 20, 30, 4020 miles
km0.5, 1, 3, 5, 10, 15, 25, 50, 10025 km
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.

Map view

When the map 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 option.

Full configuration reference

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.).
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.
const { html, data } = await jobSearch.prerender({ ...config, withData: true });