跳到主要内容

Create a room snapshot before a mass edit

This example demonstrates how to automatically create a "snapshot" of a room before a mass edit operation. The script checks whether the room has a special tag (for example, prepare-mass-edit). If the tag is present, it creates a timestamped folder in a dedicated Snapshot room and copies all top-level files from the source room into that folder. This approach helps you keep a quick backup of important room files before you run bulk renaming, indexing, permissions updates, or other large changes.

Before you start

  1. Replace https://yourportal.onlyoffice.com and YOUR_API_KEY with your actual DocSpace portal URL and API key. Ensure you have the necessary data and permissions to perform these operations.
  2. Before you can make requests to the API, you need to authenticate. Check out the Personal access tokens page to learn how to obtain and use access tokens.
Full example
// Basic configuration
const API_HOST = process.env.DOCSPACE_API_HOST; // Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
const API_KEY = process.env.DOCSPACE_API_KEY; // Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.

// Room where snapshots are stored (destination)
const SNAPSHOT_ROOM_ID = 539564;

// Tag that enables snapshot creation on a source room
const TAG_MARKER = 'prepare-mass-edit';

// Request headers
const HEADERS: Record<string, string> = {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};

// Simple request helper
async function docspaceRequest(method: string, path: string, body: any = null) {
const url = `${API_HOST}${path}`;

try {
const res = await fetch(url, {
method,
headers: HEADERS,
body: body ? JSON.stringify(body) : undefined,
});

if (!res.ok) {
const text = await res.text();
console.log(`Request failed: ${method} ${url}`);
console.log(`Status: ${res.status}, Message: ${text}`);
return null;
}

return await res.json();
} catch (err: any) {
console.log(`Request error: ${err?.message || err}`);
return null;
}
}

function getResponseNode(data: any) {
if (!data || typeof data !== 'object') return null;
if (data.response && typeof data.response === 'object') return data.response;
return data;
}

function normalizeTags(room: any) {
const raw = room?.tags ?? room?.tagList ?? [];
if (!Array.isArray(raw)) return [];
return raw.map((t: any) => String(t));
}

// Step 1: Load room details and check whether TAG_MARKER is present
async function getRoomInfo(roomId: number) {
const data = await docspaceRequest('GET', `/api/2.0/files/rooms/${roomId}`);
const node = getResponseNode(data);
return node && typeof node === 'object' ? node : null;
}

// Step 2: Create a timestamped snapshot folder in the Snapshot room
async function createSnapshotFolder(sourceRoomTitle: string) {
const ts = new Date().toISOString().replace(/[:.]/g, '-');
const snapshotTitle = `${sourceRoomTitle}_${ts}`;

const data = await docspaceRequest(
'POST',
`/api/2.0/files/folder/${SNAPSHOT_ROOM_ID}`,
{ title: snapshotTitle }
);

const node = getResponseNode(data);
if (!node || typeof node !== 'object') return null;

const folderId = node.id != null ? Number(node.id) : null;
if (!Number.isFinite(folderId)) return null;

return { folderId, snapshotTitle };
}

// Step 3: List top-level files in the source room (subfolders are skipped)
async function listTopLevelRoomFiles(roomId: number) {
const data = await docspaceRequest('GET', `/api/2.0/files/rooms/${roomId}`);
const node = getResponseNode(data);

const items =
Array.isArray(node) ? node :
node && typeof node === 'object' && Array.isArray(node.files) ? node.files :
[];

const files: { id: number; title: string }[] = [];

for (const it of items) {
if (!it || typeof it !== 'object') continue;

const isFolder =
it.isFolder === true ||
it.folder === true ||
String(it.type || '').toLowerCase() === 'folder';

if (isFolder) continue;

const id = Number(it.id);
if (!Number.isFinite(id)) continue;

files.push({
id,
title: String(it.title || ''),
});
}

return files;
}

// Step 4: Copy a file into the snapshot folder
async function copyFileToSnapshot(fileId: number, destFolderId: number, destTitle: string) {
const payload = {
destTitle,
destFolderId,
};

const data = await docspaceRequest('POST', `/api/2.0/files/file/${fileId}/copyas`, payload);
return Boolean(data);
}

// Main flow: create snapshot only if the room is tagged
async function createRoomSnapshotIfTagged(roomId: number) {
const roomInfo = await getRoomInfo(roomId);
if (!roomInfo) {
console.log(`Room not found. roomId=${roomId}`);
return;
}

const roomTitle = String(roomInfo.title || `Room ${roomId}`);
const tags = normalizeTags(roomInfo);

if (!tags.includes(TAG_MARKER)) {
console.log(`Skipping: room "${roomTitle}" does not have tag "${TAG_MARKER}".`);
return;
}

const snapshotFolder = await createSnapshotFolder(roomTitle);
if (!snapshotFolder) {
console.log('Snapshot folder could not be created.');
return;
}

const files = await listTopLevelRoomFiles(roomId);
if (!files.length) {
console.log(`No top-level files found in room "${roomTitle}".`);
return;
}

let copied = 0;

for (const f of files) {
const title = f.title || `file_${f.id}`;
const ok = await copyFileToSnapshot(f.id, snapshotFolder.folderId, title);
if (ok) copied += 1;
}

console.log(
`Snapshot created for "${roomTitle}". ` +
`Copied: ${copied}. Snapshot folder ID: ${snapshotFolder.folderId}.`
);
}

// Step 5: Example event handler (placeholder)
function handleRoomUpdatedEvent(eventPayload: any) {
const data = eventPayload && eventPayload.data ? eventPayload.data : {};
const roomId = Number(data.roomId);

if (!Number.isFinite(roomId)) {
console.log('Invalid roomId in event payload.');
return;
}

createRoomSnapshotIfTagged(roomId);
}

// Example usage
handleRoomUpdatedEvent({
event: 'room_updated',
data: { roomId: 2025512 },
});

Step 1: Check whether the room is marked for snapshot

A GET request is sent to /api/2.0/files/rooms/:roomId to load room details. The script reads the tag list (for example, tags or tagList) and checks whether it contains TAG_MARKER. If the tag is missing, the room is skipped.

async function getRoomInfo(roomId: number) {
const data = await docspaceRequest('GET', `/api/2.0/files/rooms/${roomId}`);
const node = getResponseNode(data);
return node && typeof node === 'object' ? node : null;
}

Step 2: Create a snapshot folder

A POST request is sent to /api/2.0/files/folder/:snapshotRoomId to create a folder inside the Snapshot room.

The folder title is generated like this:

  • RoomTitle_YYYY-MM-DD_HH-MM-SS

This makes it easy to find a specific snapshot later.

async function createSnapshotFolder(sourceRoomTitle: string) {
const ts = new Date().toISOString().replace(/[:.]/g, '-');
const snapshotTitle = `${sourceRoomTitle}_${ts}`;

const data = await docspaceRequest(
'POST',
`/api/2.0/files/folder/${SNAPSHOT_ROOM_ID}`,
{ title: snapshotTitle }
);

const node = getResponseNode(data);
if (!node || typeof node !== 'object') return null;

const folderId = node.id != null ? Number(node.id) : null;
if (!Number.isFinite(folderId)) return null;

return { folderId, snapshotTitle };
}

Step 3: List top-level files in the source room

Then the script sends a GET request to /api/2.0/files/rooms/:id to load the room contents. It collects only top-level files:

file ID — used to copy the file via API title — used as the destination file name

Subfolders are skipped in this example.

async function listTopLevelRoomFiles(roomId: number) {
const data = await docspaceRequest('GET', `/api/2.0/files/rooms/${roomId}`);
const node = getResponseNode(data);

const items =
Array.isArray(node) ? node :
node && typeof node === 'object' && Array.isArray(node.files) ? node.files :
[];

const files: { id: number; title: string }[] = [];

for (const it of items) {
if (!it || typeof it !== 'object') continue;

const isFolder =
it.isFolder === true ||
it.folder === true ||
String(it.type || '').toLowerCase() === 'folder';

if (isFolder) continue;

const id = Number(it.id);
if (!Number.isFinite(id)) continue;

files.push({
id,
title: String(it.title || ''),
});
}

return files;
}

Step 4: Copy files to the snapshot folder

Then it sends a POST request to /api/2.0/files/file/:fileId/copyas for each file. Each copied file is placed into the snapshot folder created in Step 2.

async function copyFileToSnapshot(fileId: number, destFolderId: number, destTitle: string) {
const payload = {
destTitle,
destFolderId,
};

const data = await docspaceRequest('POST', `/api/2.0/files/file/${fileId}/copyas`, payload);
return Boolean(data);
}