Zoey Baby imports one thing: a ZIP holding a data.json file, the same
archive its own export writes. That is also how you bring history over
from another tracker. Turn its export into this ZIP and the app reads it
like one of its own backups.
Import from Settings → Privacy & data → Import Zoey Baby ZIP, or from the Import button during first-run setup.
What’s in the ZIP
zoey-baby-family-20260918-113000.zip
└── zoey-baby-family-20260918-113000/
├── data.json
└── images/
├── 1/ the first child in data.json
│ ├── child/1.jpg
│ ├── feedings/1.jpg
│ ├── diapers/12.jpg
│ ├── pumping/3.jpg
│ ├── growth/2.jpg
│ ├── temperature/1.jpg
│ └── medication/4.jpg
└── 2/ the second child, same shape
└── feedings/1.jpg
data.json is the whole import. The single wrapping folder is what the
app’s own export produces; putting data.json and images/ at the top
of the ZIP works just as well.
An archive holds the whole family, so photos are filed per child:
images/<n>/ where n is the child’s position in children, counting
from 1. images/ is optional, and so is any folder in it. Inside, a
photo is named for the id of the row it belongs to, in the folder
named for that row’s key, so images/2/feedings/12.jpg is the photo on
the second child’s feeding with "id": 12.
data.json
A whole archive: a family of two, with a row or two under each key.
{
"exportedAt": "2026-09-18T11:30:00-04:00",
"children": [
{
"child": {
"id": "4E9C1B60-9E1E-4E6A-9C2F-6B0A1D9A77C1",
"firstName": "Zoey",
"lastName": "Robertson",
"sex": "girls",
"birthDate": "2026-03-02T08:14:00-05:00",
"photoPath": "images/1/child/1.jpg"
},
"childName": "Zoey",
"feedings": [
{
"id": 1,
"start": "2026-09-18T07:57:00-04:00",
"end": "2026-09-18T08:12:00-04:00",
"type": "breast milk",
"method": "bottle",
"amount": 120,
"notes": "sleepy"
},
{
"id": 2,
"start": "2026-09-18T11:05:00-04:00",
"end": "2026-09-18T11:27:00-04:00",
"type": "breast milk",
"method": "both breasts",
"leftSeconds": 780,
"rightSeconds": 540
}
],
"diapers": [
{ "id": 1, "time": "2026-09-18T08:12:00-04:00", "wet": true, "solid": true, "color": "mustard" }
],
"sleep": [
{ "day": "2026-09-18T00:00:00-04:00", "awakeSeconds": 31500, "asleepSeconds": 19620, "deepSeconds": 5130 }
],
"naps": [
{ "start": "2026-09-18T08:37:31-04:00", "end": "2026-09-18T10:51:31-04:00", "manual": false }
],
"pumping": [
{ "id": 1, "start": "2026-09-18T07:44:00-04:00", "end": "2026-09-18T08:04:00-04:00", "amount": 150, "leftMl": 90, "rightMl": 60 }
],
"growth": [
{ "id": 1, "date": "2026-09-14T09:00:00-04:00", "metric": "weight", "valueMetric": 6.42 }
],
"temperature": [
{ "id": 1, "time": "2026-09-17T21:10:00-04:00", "celsius": 37.4, "method": "armpit" }
],
"medication": [
{ "id": 1, "time": "2026-09-17T21:20:00-04:00", "name": "Infant Tylenol", "doseAmount": 2.5, "doseUnit": "ml" }
]
},
{
"child": { "firstName": "Wren", "sex": "boys", "birthDate": "2024-05-11T02:40:00-04:00" },
"childName": "Wren",
"feedings": [],
"diapers": [
{ "id": 1, "time": "2026-09-18T09:20:00-04:00", "wet": true, "solid": false }
],
"sleep": [],
"pumping": []
}
]
}
children is the archive. Everything else at the top level is the
app’s own bookkeeping, and a converted archive can leave it out. Keys
the app doesn’t know are ignored, so extra fields of your own are
harmless.
Keys
The top level:
| Key | Required | Holds |
|---|---|---|
children |
yes | One entry per child, each with their own records. |
preferences |
no | Units and which buttons are on. |
homeAssistant |
no | Sensor settings. |
exportedAt |
no | When the archive was written. |
Inside one entry of children:
| Key | Required | Holds |
|---|---|---|
feedings |
yes | Bottles and breast feeds, and solids. |
diapers |
yes | Diaper changes. |
sleep |
yes | One row per day of awake / asleep / deep totals. |
pumping |
yes | Pumping sessions. |
naps |
no | Individual sleeps, which the timeline draws a row each. |
growth |
no | Weights, heights and head circumferences. |
temperature |
no | Temperature readings. |
medication |
no | Doses given. |
child |
no | Name, birth date, sex and profile photo. |
childName |
no | The same first name, for the import’s own wording. |
sensors |
no | The Home Assistant entities this child’s readings come from. |
Those four required keys have to be present on every child, even when empty.
Which child an entry lands on
A child in the archive is matched to a child in the family by child.id
first, so a family restoring its own backup puts every child’s records
back where they came from, whatever they have since been renamed to.
Failing that, the name is matched. Failing that, the child is created
from the header, except for an archive of a single child that matched
nothing, which lands on whichever child is open: that is the restore
onto a fresh install, where the placeholder from setup is who the
records belong to.
A converted archive has no ids to offer, so its children are matched by
name or created. Give each one a child header with at least a
firstName.
Times are ISO 8601 with an offset (2026-09-16T08:37:31-04:00).
Volumes are millilitres, weight is kilograms, lengths are centimetres,
temperature is Celsius. The app converts to whatever units the phone is
set to. id is an integer unique within its own key, and its only job
is to attach a photo; numbering each one from 1 is fine.
feedings
| Field | Required | Notes |
|---|---|---|
id |
yes | Photo key. |
start, end |
yes | Equal for a bottle logged at a moment. |
type |
yes | breast milk, formula, fortified breast milk, solid food |
method |
yes | bottle, left breast, right breast, both breasts, parent fed, self fed |
amount |
no | Millilitres. Bottles only. |
leftSeconds, rightSeconds |
no | Time on each side of a breast feed. |
notes |
no |
diapers
| Field | Required | Notes |
|---|---|---|
id |
yes | Photo key. |
time |
yes | |
wet, solid |
yes | Both true is a mixed diaper. |
blowout |
no | Defaults to false. |
color |
no | black, brown, green, yellow, mustard |
notes |
no |
sleep
One row per day, holding that day’s totals. All four fields are
required, and day is local midnight.
| Field | Notes |
|---|---|
day |
Local midnight of the day the totals cover. |
awakeSeconds, asleepSeconds, deepSeconds |
Deep is counted inside asleep, not beside it. |
naps
Individual sleeps, which are what the timeline draws a row per. Leave the section out if the other app only kept daily totals.
| Field | Required | Notes |
|---|---|---|
start |
yes | |
end |
no | Null means still running, so leave a real end on every historical nap. |
manual |
yes | True for a nap a person logged; a sensor never moves those afterwards. |
notes |
no |
pumping
| Field | Required | Notes |
|---|---|---|
id |
yes | Photo key. |
start, end |
yes | |
amount |
no | Millilitres, both sides together. |
leftMl, rightMl |
no | Per-side split, if the other app kept one. |
notes |
no |
growth
| Field | Required | Notes |
|---|---|---|
id |
yes | Photo key. |
date |
yes | |
metric |
yes | weight, height, headCircumference |
valueMetric |
yes | Kilograms for weight, centimetres for the other two. |
notes |
no |
temperature
| Field | Required | Notes |
|---|---|---|
id |
yes | Photo key. |
time, celsius |
yes | A row missing either is skipped, since there is no reading to keep. |
method |
no | forehead, ear, armpit, oral, rectal |
notes |
no |
medication
| Field | Required | Notes |
|---|---|---|
id |
yes | Photo key. |
time, name |
yes | A row missing either is skipped. |
doseAmount |
no | |
doseUnit |
no | ml, mg, drops, tablet |
notes |
no |
child
Who the entry’s records belong to. Every field is optional, but without
a firstName there is nothing to match on and nothing to name a minted
child after.
| Field | Notes |
|---|---|
id |
The child’s id in the family that wrote the archive. Match key, see above. |
firstName, lastName |
|
sex |
girls or boys, which picks the WHO growth curve. |
birthDate |
|
photoPath |
Path inside the ZIP, images/<n>/child/1.jpg by convention. |
sensors
Which sock is this child’s. The connection they are read
through is the family’s, under homeAssistant. Written by the app’s
own export; a converted archive can leave it out.
| Field | Notes |
|---|---|
sleepStateEntity |
The sleep source. |
heartRateEntity, oxygenEntity |
Vitals. |
chargingEntity, batteryEntity |
The sock’s own state. |
trackSleep |
Whether this child’s sensor history is rolled up into daily totals. |
preferences
What the app’s own export saves so a restore doesn’t reset your settings. Leave the whole key out of a converted archive. Every field is optional, and a value the app doesn’t recognise is dropped rather than failing the import.
| Field | Notes |
|---|---|
liquidUnits, growthUnits |
metric or imperial. |
enabledTiles |
Home’s buttons, in order: bottle, breast, diaper, pump, sleep, measurement, temperature, medication. |
enabledSummaryPills |
The day strip: totalMilk, breast, diaper, pump, sleep. |
enabledLiveActivityKinds |
Which timers raise a Live Activity: breast, pump. |
enabledNotificationKinds |
Which of a partner’s logs notify, named as enabledTiles is. |
homeAssistant
The sensor settings, for the same reason. Leave it out too: the server
URL and the token are sealed into secrets, a box only the devices
signed into that iCloud account can open, so copying it between
families achieves nothing.
| Field | Notes |
|---|---|
enabled |
Whether the integration is on. |
secrets |
Base64 of the sealed box holding the server URL and token. |
The entity ids belong to a child rather than a family, so they are
written under that child’s sensors instead.
Converting another app’s export
- Export from the other app. Most give a CSV, one file or one per kind of event.
- Map its columns onto the keys above. A feed usually becomes a
feedingsrow; a diaper change adiapersrow withwetandsolidflags; a pumping session apumpingrow. Convert its volumes to millilitres, its weights to kilograms and its temperatures to Celsius as you go. - Write
data.jsonwith one entry inchildrenper child, each holding those arrays and achildheader, remembering the four keys that must be present even when empty. - Zip it, with
data.jsonat the top of the archive or inside a single folder. - Get it onto the phone, by AirDrop or by saving it to Files, and import it.
Steps 2 and 3 are the fiddly ones by hand, and they are exactly what a
modern AI tool is good at. Paste this page and a sample of your export
into ChatGPT, Claude, or whichever you use, and ask it to convert the
whole file to Zoey Baby’s data.json format. Give it the real export
rather than a description of it, and check the result before importing:
the row counts should match your export, and the first and last dates
should be the ones you expect.
A row that matches an event already stored, by kind and timestamp, updates that event rather than adding a second one. So a correction is just a fixed file imported again, and an archive that overlaps history you already have won’t double it up.
If you want a real archive to compare against, export from Zoey Baby first: Settings → Privacy & data → Export. The ZIP it writes is exactly the shape described here.