Luna HR Docs

Data Import

This guide covers the technical details of Luna HR's CSV import — field specifications, mapping rules, and how to handle common issues.

For a general overview, see Importing Data.

Import process

  1. Select data type — org structure, employees, leave, expenses, assets, or training
  2. Upload CSV — drag and drop or click to browse
  3. Map columns — match your CSV headers to Luna HR fields
  4. Preview — review each row with validation feedback
  5. Import — create the records

Import order

Some imports depend on others. The wizard lists them in the order they should be run:

  1. Org structure — offices, departments, teams
  2. Employees — references roles and org nodes
  3. Leave, expenses, training — reference employees
  4. Assets — independent, but assignment references employees

Field specifications

Org structure fields

| Field | Required | Format | Notes | |-------|----------|--------|-------| | Name | Yes | Text | | | Type | Yes | Text | company, region, office, division, department, team, cost_centre, project, custom | | Parent | No | Text | Name of the parent node. May be another row of the same file — parents are created before their children regardless of row order. Unresolvable parents attach to the company root and are reported | | Code | No | Text | | | Description | No | Text | |

A node is a duplicate only if a node with the same name and type already exists under the same parent — so a "Support" team in each office is fine.

Employee fields

| Field | Required | Format | Notes | |-------|----------|--------|-------| | First name | Yes | Text | | | Last name | Yes | Text | | | Email | Yes | Email | Must be unique within the company. Stored lower-cased — this is the address the employee signs in with | | Employee # | Yes | Text | Must be unique within the company | | Start date | Yes | Date | YYYY-MM-DD or DD/MM/YYYY | | Employment type | Yes | Text | full-time, part-time, contractor, intern | | Contract type | Yes | Text | permanent, fixed-term, casual | | Role | Yes | Text | Must match an existing permission role name. Not auto-matched from a "Job Title" column — see below | | FTE | Yes | Number | Between 0 and 1 | | Status | Yes | Text | active, inactive, onboarding, offboarding, leaver, suspended. "active" is stored as "invited" until the employee creates their login | | Job title | No | Text | Display label only. Created on the fly if it doesn't exist yet | | Department | No | Text | Must match an existing org node name | | Line manager | No | Text | Match by email or full name | | Working pattern | No | Text | Must match an existing pattern name | | End date | No | Date | Must not be before the start date | | Payroll reference | No | Text | | | Emergency contact name / phone / relationship | No | Text | A contact record is created when both a name and a phone are present | | NI number, address, demographics | No | Text | See the mapping step for the full list |

Role vs Job title. Role is the permission tier and decides what the person can do in Luna; Job title is the label shown on their profile. A "Job Title" column is deliberately not auto-mapped onto Role — mapping it there would grant permissions from a spreadsheet label. Map both explicitly if your export has one column serving both purposes.

Leave fields

| Field | Required | Format | Notes | |-------|----------|--------|-------| | Employee | Yes | Text | Match by email or full name | | Leave type | Yes | Text | Must match an existing leave type | | Start date | Yes | Date | | | End date | Yes | Date | | | Days | Yes | Number | Must be greater than 0 | | Status | Yes | Text | approved, pending, rejected, cancelled, taken, draft | | Notes | No | Text | |

The end date must not be before the start date. Approved and taken leave adjusts the employee's taken / booked counters; pending leave is reserved against pending. Entitlement itself always comes from the resolved leave policy — the import never sets it.

Expense fields

| Field | Required | Format | Notes | |-------|----------|--------|-------| | Employee | Yes | Text | Match by email or full name | | Expense date | Yes | Date | | | Category | Yes | Text | Must match an existing expense category | | Amount | Yes | Number | Currency symbols and commas are stripped automatically | | Status | Yes | Text | draft, submitted, approved, rejected, paid | | Report title | No | Text | Items with same employee + title are grouped into one report | | Description | No | Text | | | Currency | No | Text | Defaults to GBP | | Tax rate | No | Number | Percentage (e.g. 20 for 20%) | | Payment reference | No | Text | | | Payment method | No | Text | bank_transfer, payroll, petty_cash |

Asset fields

| Field | Required | Format | Notes | |-------|----------|--------|-------| | Name | Yes | Text | | | Asset tag | Yes | Text | Must be unique | | Category | Yes | Text | Must match an existing asset category | | Status | Yes | Text | available, assigned, maintenance, retired, disposed | | Condition | Yes | Text | new, good, fair, poor | | Serial number | No | Text | | | Manufacturer | No | Text | | | Model | No | Text | | | Assigned to | No | Text | Match by email or full name | | Purchase date | No | Date | | | Purchase cost | No | Number | | | Warranty expiry | No | Date | |

Training fields

| Field | Required | Format | Notes | |-------|----------|--------|-------| | Employee | Yes | Text | Match by email or full name | | Course name | Yes | Text | Must match an existing course, or create one from the preview step | | Completion date | Yes | Date | | | Status | Yes | Text | passed, failed, incomplete | | Duration (hours) | Yes | Number | | | Provider, course code, valid from/until, cost, score, certificate # | No | | See the mapping step |

Common source-system wordings are translated: "completed" and "pass" become passed, "in progress" and "not started" become incomplete. Anything else is reported so you can map it explicitly rather than have it guessed at.

Column matching

Luna HR automatically matches common column header names. For example:

  • "Staff Name", "Employee Name", "Name" → Employee
  • "Date", "Receipt Date", "Transaction Date" → Expense Date
  • "Cost", "Total", "Value", "Price" → Amount

Matching is exact-alias first, then a fuzzy pass restricted to aliases of five or more characters, so short generic tokens ("type", "name", "mail") can't hijack a longer header.

If auto-matching doesn't find the right column, set the mapping yourself in the mapping step. Two columns cannot point at the same Luna field — only one would be imported, so the wizard blocks until you skip the extras.

Validation

Before importing, every row is validated:

  • Errors (red) — must be fixed before import. Examples: required field missing, employee not found, duplicate email or employee number, a value outside the allowed list for that column, an end date before its start date
  • Warnings (amber) — can proceed but may need attention. Example: a line manager who isn't in Luna yet (the link can be made later)

Rows the import would skip are errors, not warnings, so the count on the "Import N records" button is the number you actually get.

You can edit rows inline in the preview to fix issues without going back to your CSV.

Tips

  • Create categories first — expense categories, asset categories, and leave types must exist before you import data that references them
  • Match employee names — the import resolves employees by email first, then full name. Ensure your CSV uses the same names as Luna HR
  • Test small — import 10-20 rows first to verify your column mapping
  • Dates — YYYY-MM-DD is preferred. DD/MM/YYYY also works and is read day first, so a US-style 06/15/2024 is rejected rather than misread. Impossible dates (31 February) are rejected too
  • Quoted fields — commas, quotes, and line breaks inside a quoted field are handled, so multi-line addresses import intact
  • Amounts — currency symbols (GBP, $, EUR) and thousand separators (commas) are handled automatically