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
- Select data type — org structure, employees, leave, expenses, assets, or training
- Upload CSV — drag and drop or click to browse
- Map columns — match your CSV headers to Luna HR fields
- Preview — review each row with validation feedback
- Import — create the records
Import order
Some imports depend on others. The wizard lists them in the order they should be run:
- Org structure — offices, departments, teams
- Employees — references roles and org nodes
- Leave, expenses, training — reference employees
- 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/2024is 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
Related
- Importing Data — overview of the import process
- Employee Management — managing employees after import