Luna HR Docs

Job Board Embed

The Luna HR job board is embeddable on any external website — your marketing site, a Webflow page, a WordPress careers page — with a single script tag. The embed renders an iframe that lists your currently-open public roles and links each one to its public application page.

Quick start

Drop this snippet wherever you want the job list to appear:

<div id="luna-jobs"></div>
<script
  src="https://app.lunahr.co.uk/embed/jobs.js"
  data-company="YOUR_COMPANY_ID"
  data-theme="auto"
></script>

That's it. The widget will:

  1. Find the <div id="luna-jobs"> mount point
  2. Inject a sandboxed iframe that loads your jobs from Luna HR
  3. Auto-resize itself as the content height changes
  4. Link each role to its public application page on app.lunahr.co.uk/jobs/[token]

If <div id="luna-jobs"> is missing, the widget creates one immediately after the script tag.

Finding your company ID

Your company ID is a Convex document ID — a 32-character string like kn76gpbtg12c6qe7wvpyqn60m5842nb2. To find yours:

  1. Open Luna HR and go to Admin → Recruitment Settings
  2. Copy the value shown under Embed code

Configuration

All options are passed as data-* attributes on the <script> tag.

| Attribute | Required | Default | Notes | |-----------|----------|---------|-------| | data-company | Yes | — | Your Luna HR company ID | | data-theme | No | light | light, dark, or auto (follows visitor's OS dark mode) | | data-accent | No | Your careers brand colour | CSS hex colour, e.g. #7c3aed | | data-max | No | unlimited | Max number of jobs to show; surplus collapses to a "View all" link | | data-target | No | #luna-jobs | CSS selector for the mount element |

Examples

Auto-follow OS dark mode

<script
  src="https://app.lunahr.co.uk/embed/jobs.js"
  data-company="..."
  data-theme="auto"
></script>

Custom accent colour, top 5 roles only

<script
  src="https://app.lunahr.co.uk/embed/jobs.js"
  data-company="..."
  data-theme="light"
  data-accent="#0ea5e9"
  data-max="5"
></script>

Mount somewhere other than #luna-jobs

<div class="my-careers-list"></div>
<script
  src="https://app.lunahr.co.uk/embed/jobs.js"
  data-company="..."
  data-target=".my-careers-list"
></script>

Live theme switching

Sites with a runtime theme toggle (sun/moon button) can push theme changes into the iframe so it switches alongside the rest of the page. The widget listens for postMessage events on the iframe's window:

const iframe = document.querySelector('#luna-jobs iframe')
iframe.contentWindow.postMessage(
  { type: "luna-embed-theme", theme: "dark" }, // or "light"
  "*"
)

The widget validates the message shape and ignores anything that doesn't match. A typical pattern is to send the message:

  1. On the iframe's load event (initial sync)
  2. Whenever your site's theme class changes (a MutationObserver on <html> works well)

If you set data-theme="auto", the iframe also follows prefers-color-scheme automatically — useful as a sensible default before your first message arrives.

Iframe resize protocol

You don't need to do anything for resizing — the widget handles it. For reference, the iframe posts its content height to its parent whenever the layout changes:

window.parent.postMessage({ type: "luna-embed-resize", height: 320 }, "*")

The bundled jobs.js script listens for this and updates iframe.style.height accordingly.

What's served

The embed only ever returns:

  • Job title, slug, employment type, location type, location, closing date
  • Short description
  • Salary range — only if the role's "Show salary" toggle is enabled
  • The public application token

Hidden, archived, or draft roles are never returned. The endpoint is public and CORS-open by design (Access-Control-Allow-Origin: *), but rate-limited to prevent abuse.

Security and CSP

If your site has a Content Security Policy, allow Luna's app domain in the relevant directives:

script-src ... https://app.lunahr.co.uk;
frame-src  ... https://app.lunahr.co.uk;

The iframe itself is sandboxed (allow-scripts allow-same-origin allow-popups) and its responses ship with a tight CSP — see the response headers on app.lunahr.co.uk/embed/jobs if you want to verify.

Troubleshooting

The widget shows "Unable to load job listings." Check that your data-company value is correct and that you have at least one role with Status: Open and Public: Yes.

The widget shows "No open roles right now." That's the empty state — it appears whenever there are no public, open roles. Edit a role and toggle it to Public to see it appear.

The iframe stays small / doesn't resize. The widget uses postMessage for resize. If your site's CSP blocks frame-src https://app.lunahr.co.uk or your CSP forbids script-src 'unsafe-inline', the parent page can't process the height messages. Check the browser console.

The theme doesn't change with my site's toggle. Make sure you're calling iframe.contentWindow.postMessage(...), not window.postMessage(...) — the latter posts to the parent's own window and the iframe never sees it.