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:
- Find the
<div id="luna-jobs">mount point - Inject a sandboxed iframe that loads your jobs from Luna HR
- Auto-resize itself as the content height changes
- 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:
- Open Luna HR and go to Admin → Recruitment Settings
- 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:
- On the iframe's
loadevent (initial sync) - Whenever your site's theme class changes (a
MutationObserveron<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.