Skip to content

Instantly share code, notes, and snippets.

@pixelspark
Created January 10, 2026 12:20
Show Gist options
  • Select an option

  • Save pixelspark/71703a23ca6b74817154ce3134cea40b to your computer and use it in GitHub Desktop.

Select an option

Save pixelspark/71703a23ca6b74817154ce3134cea40b to your computer and use it in GitHub Desktop.
Make albums from folders and set people birth dates from VCF in Immich
// Run: node immich-fill.ts (node v24 or above)
const url = "https://immich-url-here/api/";
const key = "PASTE API KEY HERE";
// Paste path to VCF file here (generate it with Apple Contacts -> export)
const vcfPath = "/Users/tommy/Desktop/contacts.vcf";
import { readFile } from "node:fs/promises";
async function request(path: string, options?: Record<string, any>) {
let opts = options ?? {};
if (!opts.headers) opts.headers = {};
opts.headers["x-api-key"] = key;
const res = await fetch(url + path, opts);
return await res.json();
}
async function put(path: string, method: string, body: any) {
return request(path, {
method,
headers: {
"Content-type": "application/json",
},
body: JSON.stringify(body),
});
}
async function makeAlbums() {
const albums: any[] = await request("albums");
const paths: string[] = await request("view/folder/unique-paths");
// Sort albums by name
const albumIdsByName = new Map<string, string>();
for (const album of albums) {
albumIdsByName.set(album.albumName, album.id);
}
console.log("Existing albums", albumIdsByName);
for (const path of paths) {
if (!path.startsWith("/pictures")) continue;
const parts = path.split("/");
if (parts.length < 4) continue;
const albumName = parts[3].trim();
if (!albumIdsByName.has(albumName)) {
// Create album
const res = await put("albums", "POST", { albumName });
albumIdsByName.set(albumName, res.id);
}
const albumID = albumIdsByName.get(albumName)!;
// Now add all assets in this folder
const assets = await request(
"view/folder?path=" + encodeURIComponent(path)
);
const ids: string[] = assets.map((x: any) => x.id);
await put("albums/assets", "PUT", {
albumIds: [albumID],
assetIds: ids,
});
}
console.log("New albums", albumIdsByName);
}
async function setBirthDates() {
const data = (await readFile(vcfPath)).toString("utf8");
const lines = data.split(/(\r\n|\n|\r)/);
const birthDays = new Map<string, string>();
let name = null;
for (const line of lines) {
if (line === "BEGIN:VCARD") {
name = null;
continue;
}
const cmd = line.split(/\:/);
if (cmd[0] === "FN") {
name = cmd[1];
} else if (cmd[0] === "BDAY") {
if (name !== null) {
birthDays.set(name, cmd[1]);
name = null;
}
}
}
const people: any[] = await request("people");
const peopleIdByName = new Map<string, string>();
console.log(people);
for (const person of people.people) {
if (person.birthDate) continue;
if (person.name) {
peopleIdByName.set(person.name, person.id);
}
}
for (const [name, birthDay] of birthDays) {
const personID = peopleIdByName.get(name);
console.log(name, birthDay, personID);
if (personID) {
console.log(
"SET BD",
await put("people/" + encodeURIComponent(personID), "PUT", {
birthDate: birthDay,
})
);
}
// Let's find the person ID
}
}
// Enable whatever you want to do
setBirthDates();
//makeAlbums();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment