Skip to content

Instantly share code, notes, and snippets.

@erbanku
Last active March 12, 2026 03:04
Show Gist options
  • Select an option

  • Save erbanku/e62b2f4cdf828ad1dc9de6b4d53341b5 to your computer and use it in GitHub Desktop.

Select an option

Save erbanku/e62b2f4cdf828ad1dc9de6b4d53341b5 to your computer and use it in GitHub Desktop.
Dify.AI Workflows Exporter Tool

export-dify.ai-workflows.js

Export all Dify workflows/apps as YAML and download them as a ZIP.

Run

Open Dify Console (dify.ai) → DevTools → Console and run full script export-dify.ai-workflows.js

Output

YAML files and ZIP are prefixed with the export date:

[dify.ai-YYYYMMDD]workflow-name.yaml
[dify.ai-YYYYMMDD]dify_apps_yaml.zip

Notes

  • Uses your current logged-in Dify session
  • No API key required
  • Runs entirely in the browser
(() => {
const LOG = '[Dify Batch Export]';
const encoder = new TextEncoder();
const log = (...args) => console.log(LOG, ...args);
const err = (...args) => console.error(LOG, ...args);
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const getCookie = (name) => {
const match = document.cookie.match(
new RegExp(`(?:^|; )${escapeRegExp(name)}=([^;]*)`)
);
return match ? decodeURIComponent(match[1]) : null;
};
const getApiBase = () => {
const entries = performance.getEntries().map(e => e.name).filter(Boolean);
for (const name of entries) {
try {
const url = new URL(name, location.origin);
if (url.pathname.includes('/console/api')) {
return `${url.origin}/console/api`;
}
} catch {}
}
for (const name of entries) {
try {
const url = new URL(name, location.origin);
if (url.pathname.includes('/api')) {
return `${url.origin}/api`;
}
} catch {}
}
return `${location.origin}/console/api`;
};
const getAuthHeaders = () => {
const headers = {};
const csrfNames = [
'__Host-csrf_token',
'csrf_token',
'csrf-token',
'x-csrf-token'
];
const csrfHit = csrfNames
.map(name => [name, getCookie(name)])
.find(([, value]) => Boolean(value));
if (csrfHit) {
const token = csrfHit[1];
headers['X-CSRF-Token'] = token;
headers['X-CSRFToken'] = token;
return headers;
}
const consoleToken =
localStorage.console_token ||
sessionStorage.console_token ||
window.console_token;
if (consoleToken) {
headers.Authorization = `Bearer ${consoleToken}`;
}
return headers;
};
const sanitizeFileName = (name) => {
const cleaned = (name || 'unnamed-app')
.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_')
.replace(/\s+/g, ' ')
.trim();
return cleaned || 'unnamed-app';
};
const getDateTag = () => {
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}${mm}${dd}`;
};
const getPrefix = () => `[dify.ai-${getDateTag()}]`;
// ===== ZIP: Pure frontend STORE mode, no third-party library =====
const buildCrcTable = () => {
const table = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let k = 0; k < 8; k++) {
c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
}
table[i] = c >>> 0;
}
return table;
};
const CRC_TABLE = buildCrcTable();
const crc32 = (bytes) => {
let c = 0xFFFFFFFF;
for (let i = 0; i < bytes.length; i++) {
c = CRC_TABLE[(c ^ bytes[i]) & 0xFF] ^ (c >>> 8);
}
return (c ^ 0xFFFFFFFF) >>> 0;
};
const u16 = (n) => new Uint8Array([n & 0xFF, (n >>> 8) & 0xFF]);
const u32 = (n) =>
new Uint8Array([
n & 0xFF,
(n >>> 8) & 0xFF,
(n >>> 16) & 0xFF,
(n >>> 24) & 0xFF
]);
const concat = (parts) => {
const total = parts.reduce((sum, part) => sum + part.length, 0);
const out = new Uint8Array(total);
let offset = 0;
for (const part of parts) {
out.set(part, offset);
offset += part.length;
}
return out;
};
const toDosTimeDate = (date = new Date()) => {
const year = Math.max(1980, date.getFullYear());
const dosTime =
(date.getHours() << 11) |
(date.getMinutes() << 5) |
Math.floor(date.getSeconds() / 2);
const dosDate =
((year - 1980) << 9) |
((date.getMonth() + 1) << 5) |
date.getDate();
return { dosTime, dosDate };
};
const buildZip = (files) => {
const localParts = [];
const centralParts = [];
let localOffset = 0;
for (const file of files) {
const nameBytes = encoder.encode(file.name);
const dataBytes =
typeof file.data === 'string' ? encoder.encode(file.data) : file.data;
const crc = crc32(dataBytes);
const { dosTime, dosDate } = toDosTimeDate(file.date || new Date());
const flags = 0x0800;
const compression = 0;
const localHeader = concat([
u32(0x04034b50),
u16(20),
u16(flags),
u16(compression),
u16(dosTime),
u16(dosDate),
u32(crc),
u32(dataBytes.length),
u32(dataBytes.length),
u16(nameBytes.length),
u16(0),
nameBytes,
dataBytes
]);
localParts.push(localHeader);
const centralHeader = concat([
u32(0x02014b50),
u16(20),
u16(20),
u16(flags),
u16(compression),
u16(dosTime),
u16(dosDate),
u32(crc),
u32(dataBytes.length),
u32(dataBytes.length),
u16(nameBytes.length),
u16(0),
u16(0),
u16(0),
u16(0),
u32(0),
u32(localOffset),
nameBytes
]);
centralParts.push(centralHeader);
localOffset += localHeader.length;
}
const localData = concat(localParts);
const centralDirectory = concat(centralParts);
const endRecord = concat([
u32(0x06054b50),
u16(0),
u16(0),
u16(files.length),
u16(files.length),
u32(centralDirectory.length),
u32(localData.length),
u16(0)
]);
return new Blob([localData, centralDirectory, endRecord], {
type: 'application/zip'
});
};
const uniqueFileName = (used, base, suffix = '.yaml') => {
let name = `${base}${suffix}`;
if (!used.has(name)) {
used.add(name);
return name;
}
let i = 2;
while (used.has(`${base} (${i})${suffix}`)) i += 1;
name = `${base} (${i})${suffix}`;
used.add(name);
return name;
};
const downloadBlob = (blob, fileName) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 30000);
};
const run = async () => {
const apiBase = getApiBase();
const authHeaders = getAuthHeaders();
const limit = 100;
const prefix = getPrefix();
const fetchJson = async (url) => {
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
cache: 'no-store',
headers: authHeaders
});
if (!response.ok) {
let body = '';
try {
body = await response.text();
} catch {}
throw new Error(
`${response.status} ${response.statusText}${
body ? ` | ${body.slice(0, 300)}` : ''
}`
);
}
return response.json();
};
log('API_BASE =', apiBase);
// Read all apps (auto pagination)
const apps = [];
let page = 1;
while (true) {
const payload = await fetchJson(
`${apiBase}/apps?page=${page}&limit=${limit}&name=&is_created_by_me=false`
);
const items = payload.data || payload.items || [];
apps.push(...items);
log(`Read page ${page}, ${items.length} apps, total ${apps.length}`);
const hasMore = Boolean(payload.has_more ?? payload.has_next);
if (!hasMore || items.length === 0) break;
page += 1;
}
if (!apps.length) {
throw new Error('No apps retrieved');
}
const files = [];
const failed = [];
const usedNames = new Set();
for (let i = 0; i < apps.length; i++) {
const app = apps[i];
try {
const payload = await fetchJson(
`${apiBase}/apps/${app.id}/export?include_secret=false`
);
const yaml = payload.data;
if (typeof yaml !== 'string' || !yaml.trim()) {
throw new Error('Export result empty');
}
const base = sanitizeFileName(app.name || `app-${app.id}`);
const fileName = uniqueFileName(usedNames, `${prefix}${base}`);
files.push({
name: fileName,
data: yaml,
date: new Date()
});
log(`Export success ${i + 1}/${apps.length}: ${fileName}`);
} catch (e) {
failed.push({
app: app.name || app.id,
error: e.message
});
err(`Export failed ${i + 1}/${apps.length}`, e);
}
}
if (!files.length) {
throw new Error('All exports failed');
}
const zipBlob = buildZip(files);
const zipName = `${prefix}dify_apps_yaml.zip`;
downloadBlob(zipBlob, zipName);
console.group(`${LOG} Completed`);
console.log('Success:', files.length);
console.log('Failed:', failed.length);
if (failed.length) console.table(failed);
console.groupEnd();
alert(
`Export completed: ${files.length} success, ${failed.length} failed.\nDownload started: ${zipName}`
);
};
run().catch((e) => {
err('Overall failure:', e);
alert(`Export failed: ${e.message}`);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment