How to build a job directory and search page

Combine context, filters, numbered or cursor pagination, job cards, and canonical URLs.

A listing page should be driven by URL state so search engines and users can share the same result set.

Parse public URL state

Use the SDK’s filter vocabulary instead of accepting arbitrary values. Invalid public query parameters are dropped rather than throwing.

Browse uses GET board.jobs.list; free-text search uses POST board.jobs.search. Both return job cards with data, hasMore, and nextCursor. Catalog responses can also include count, limit, offset, and gatedCount.

To filter by the board's own job or company custom fields, such as an opportunity type or an employer type, follow Custom field filters and badges.

Format cards in the board language

Load board.context() once for the board language. Pass that language into every label-producing helper.

Filter and badge by custom fields

Job custom fields can drive both listing filters and card badges. The definitions come from board.context().customFields.job: use each option's label as the control text and keep the option key in the URL (/jobs?work_type=contract), because labels change when the operator renames an option and keys do not. Send the selected keys as filters.customFields on board.jobs.search, and drop any URL value that is not a current option so it never reaches the API.

Every job card carries customFieldValues for the job and company.customFieldValues for its company, so a badge needs no extra request per job. Only single-select, multi-select, yes/no and number values appear on cards; text fields stay on the full job and company, and private company fields never appear. Select values are option keys, so resolve them to labels before rendering. For job values, resolveCustomFieldDisplay does this against the job definitions:

Load both sets of definitions once per page, not once per card. Drop a value whose key or option has no current definition rather than printing the raw key.

Build the page head from the same result

Do not calculate metadata from different filters or a separate result set. The visible heading, result count, canonical path, and structured data should describe the same page.

Map head.meta and head.links into the framework’s metadata API. Serialize each JSON-LD object safely into an application/ld+json script.

Use numbered pages for direct navigation

Most public directories should expose stable page URLs:

Preserve every active filter when generating ?page=2. Reset the page to 1 when the visitor changes a filter. Use the response’s hasMore, count, and gatedCount when present; do not fabricate a total.

Give each numbered page its own self-referencing canonical: /jobs for page 1 and /jobs?page=2 for page 2. Do not canonicalize the whole sequence to page 1. Render previous and next navigation as real <a href> links so crawlers and keyboard users can follow the sequence; a button-only “Load more” control is not a crawl path. See Google’s pagination guidance.

Treat arbitrary search, sort, and filter combinations as discovery states. They should normally use noindex,follow, while the unfiltered directory and each numbered page remain indexable. Index a filtered route only when you have deliberately created a stable landing page with a canonical path, unique useful content, internal links, and enough matching jobs.

Add a search distance to city pages

A city page such as /jobs/locations/houston-tx-united-states reads better when it also lists jobs a short commute away: "Showing 1–20 jobs within 25 mi ⌄ of Houston". The jobs API has no default distance, so your page decides it and passes radius in kilometres.

Follow the same rules as hosted boards so search engines see one page per place:

  • Show the menu only when canWiden is true. Region and country pages, and places without coordinates, list jobs in the place and ignore within.
  • Offer "Exact location only" (within=0) and the five presets. When the visitor picks the default, drop within so the plain URL stays the default view. Treat an unknown within value as the default.
  • Keep within when the visitor pages through results or changes a filter on the same place.
  • Index only the plain URL. Any URL with a valid within value gets noindex, follow and a canonical that points at the plain URL.
  • Mark a city page noindex, follow when the place has no jobs of its own, even at the default distance, so it does not rank on a neighbour's jobs. Read the place's own count from jobCount in board.taxonomy.places.list, not from the widened page.count.
  • Leave sitemaps on in-place counts; distance views never appear there.

See Styling and formatting for the distance helpers.

Use cursors for load-more and traversal

Echo nextCursor into the next request while preserving the filters. If the first request uses offset, drop it before following a cursor—paginate() does this automatically.

For load-more interfaces, echo nextCursor unchanged. For a background walk, use paginate. Do not translate a cursor into a page number or combine it with a non-zero offset.

Offer weekly alerts without requiring an account

When job alerts are enabled in board.context().features, let a visitor subscribe from the current search state. Anonymous subscriptions require affirmative consent and use double opt-in.

The confirmation link supplies the token for board.jobAlerts.confirm({ token }). Do not imply that the alert is active before confirmation. Digest links should lead to the token-authenticated manage experience where the subscriber can update, unsubscribe, or delete the preference. Weekly is the only supported frequency.

Verify the page

  1. Copy a filtered URL into a new browser and confirm it renders the same selection.
  2. Test no results, invalid filters, a gated count, and a failed search dependency.
  3. Open page 1 and page 2 directly, then follow a cursor from the same filtered result; confirm neither path repeats an item because an old offset remained.
  4. Inspect the canonical and ItemList URLs against the visible cards.
  5. Navigate the pagination using only a keyboard and confirm a crawler can discover page 2 from an anchor href in the initial HTML.
  6. Subscribe anonymously, confirm through the emailed token, and verify that manage and unsubscribe actions work without creating an account.
  7. Open a city page and confirm the default distance (25 mi or 50 km) is applied with no within in the URL. Pick "Exact location only" and a preset, and confirm both URLs are noindex, follow with the plain URL as canonical. Open a region page and confirm it shows no distance menu.

Production notes

  • Show gated counts honestly when a paywall limits results.
  • Abort superseded interactive searches.
  • Keep filters within the Board API’s supported vocabulary.
  • Use an explicit sort when stable iteration matters; the default relevance ranking can change as jobs or featured status change.
  • Give the result count or loading region a polite status announcement, preserve focus when filters update, and keep every filter paired with a visible label.

See the Jobs reference for the complete query and response contract.