Guide · For developers
Back up Framer CMS to JSON with the Server API
Framer's Server API (beta) lets plain Node read CMS collections outside the editor. This script saves every collection, with its field IDs, names and types and all items, to a timestamped JSON file. We ran it against a test project before publishing; the real output is below.
1. Get a Server API key
In your Framer project, open Site Settings → General and generate a Server API key. Each key belongs to one project. Treat it like a password and keep it out of git.
2. Install the client
mkdir framer-backup && cd framer-backup
npm init -y
npm i framer-api3. Save the script as backup.mjs
// backup.mjs — dump every Framer CMS collection to a timestamped JSON file.
// Usage: FRAMER_PROJECT_URL=... FRAMER_API_KEY=... node backup.mjs
import { connect } from "framer-api";
import { writeFile } from "node:fs/promises";
// Image/file values are class instances; pull out the plain fields we need.
function plain(entry) {
const { type, value } = entry;
if (value == null) return { type, value: null };
if (type === "array") {
return {
type,
value: value.map((row) => ({
id: row.id,
fieldData: Object.fromEntries(
Object.entries(row.fieldData).map(([k, v]) => [k, plain(v)])
),
})),
};
}
if (type === "image" || type === "file") {
return { type, value: { id: value.id, url: value.url, altText: value.altText ?? null } };
}
return { type, value };
}
const framer = await connect(process.env.FRAMER_PROJECT_URL, process.env.FRAMER_API_KEY);
try {
const project = await framer.getProjectInfo();
const collections = [];
for (const col of await framer.getCollections()) {
const fields = (await col.getFields()).map((f) => ({ id: f.id, name: f.name, type: f.type }));
const items = (await col.getItems()).map((item) => ({
id: item.id,
slug: item.slug,
draft: item.draft,
fieldData: Object.fromEntries(
Object.entries(item.fieldData).map(([k, v]) => [k, plain(v)])
),
}));
collections.push({ id: col.id, name: col.name, fields, items });
console.log(`${col.name}: ${items.length} items`);
}
const file = `framer-cms-${new Date().toISOString().replace(/[:.]/g, "-")}.json`;
await writeFile(file, JSON.stringify({ project: project.name, capturedAt: new Date().toISOString(), collections }, null, 2));
console.log(`Saved ${file}`);
} finally {
await framer.disconnect();
}
4. Run it
FRAMER_PROJECT_URL="https://framer.com/projects/Your-Project--abc123" \
FRAMER_API_KEY="your-server-api-key" \
node backup.mjsOutput from our test project:
Articles: 5 items
Categories: 4 items
Saved framer-cms-2026-10-21T11-00-45-897Z.jsonEach item looks like this. The keys are Framer's internal field IDs; each collection's fields array maps them to names.
{
"id": "GBWtQ8MxL",
"slug": "importing-content",
"draft": false,
"fieldData": {
"hkOz0IoTN": { "type": "string", "value": "Importing Content" },
"MCRifCdNq": { "type": "date", "value": "2026-04-26T04:00:00.000Z" },
"iEhHkYVSy": {
"type": "image",
"value": {
"id": "aNsAT3jCvt4zglbWCUoFe33Q.jpg",
"url": "https://framerusercontent.com/images/aNsAT3jCvt4zglbWCUoFe33Q.jpg",
"altText": "Purple Flower"
}
},
"b5ObpWg_q": { "type": "multiCollectionReference", "value": ["cms", "pro-tips"] },
"u5DEbEnly": { "type": "formattedText", "value": "<h2 dir=\"auto\">Prepare your CSV file</h2>..." }
}
}What the file does and does not cover
Images are URLs, not files
The JSON holds framerusercontent.com links. If you need the original media, download and keep it separately.
Rich text is HTML
Formatted text fields come back as HTML strings, which is the form you would write back later.
References come back as slugs
Reading a reference field returns the slugs of the linked items, while writing one back expects item IDs. A restore has to map slugs to the new IDs, so restore the referenced collections first.
Field metadata is partial
The script keeps each field's ID, name and type only. It does not save enum options or which collection a reference field points to, so it is not enough on its own to rebuild a collection's schema.
A JSON file is not a restore
To recover, you write items back through the same API. Rehearse that on a duplicate project before you need it on a live site.
Schedule it and keep versions
A backup you ran once months ago will not help much. Run the script from a scheduled job and keep several files so you can compare them.
If you would rather not maintain a script
FrameVault uses the same API to run scheduled snapshots, keep version history, show what changed between snapshots and restore individual items. One site is free. The script above is a good choice if you are happy to own the scheduled job. Read the backup options comparison for the trade-offs.