# Backup Script - All flows and split them

**URL:** <https://community.homey.app/t/backup-script-all-flows-and-split-them/159140>\
**Category:** Off-Topic\
**Created:** [September 3, 2026, 9:37pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140 "2026-09-03T21:37:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 3, 2026, 9:37pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/1 "2026-09-03T21:37:26Z")

</div>

Since I’m not really happy with the backup function in homey, I did ask Gemini to help create a script to get all flows and split them up, in separated folders/files as backup.

Just run the “api script” in the api-playground:  
[Homey Developer Tools](https://tools.developer.homey.app/tools/api-playground).

Then run the local “split script” file in cli/terminal, like this: “python3 split\_flows.py” to split them in to standard and advanced separated flows: folders/files.

**Update: 4**  
_Made the api script output flow names more unique, in the script only.  
So the split script is change aswell_

This Api Script will generate a file that saves to your computer, with all the flow output in a .txt file named “all\_flows.txt”

Now, all you need to do is run the split script file in the same folder as the saved file “all\_flows.txt”  
There is a long name version, and a short name version

Long name: name + id in the flow name  
Short name: name only in the flow name

## Api Script

```auto
async function exportHomeyFlows() {
    try {
        let output = "";
        
        // Fetch folders and flows
        const flowFolders = await Homey.flow.getFlowFolders().catch(() => ({}));
        const flows = await Homey.flow.getFlows();
        const advancedFlows = await Homey.flow.getAdvancedFlows();
        
        // Helper function to robustly resolve nested folder paths
        function getFolderPath(folderId) {
            if (!folderId) return "Root";
            
            let path = [];
            let currentId = folderId;
            let safetyCounter = 0; // Prevent infinite loops if data is circular
            
            while (currentId && flowFolders[currentId] && safetyCounter < 10) {
                const folder = flowFolders[currentId];
                path.unshift(folder.name);
                
                // Check various possible parent property names used in different Homey API versions
                currentId = folder.folder || folder.parent || folder.parentFolder;
                safetyCounter++;
            }
            
            // If the folder ID exists in our list but wasn't caught above, or couldn't resolve
            if (path.length === 0 && flowFolders[folderId]) {
                return flowFolders[folderId].name;
            }
            
            return path.length > 0 ? path.join(" / ") : "Root";
        }
        
        output += `--- FOUND ${Object.keys(flows).length} STANDARD FLOWS ---\n\n`;
        
        // Output Standard Flows
        for (const id in flows) {
            const flow = flows[id];
            
            const folderName = getFolderPath(flow.folder);
            const orderedFlow = { type: "standard", folderName: folderName, ...flow };
            
            const safeFolderName = folderName.replace(/[/\\?%*:|"<>]/g, '-');
            const safeFlowName = flow.name.replace(/[/\\?%*:|"<>]/g, '-');
            
            output += `_.:._ START_FLOW: [${safeFolderName}] ${safeFlowName} (${flow.id}) _.:._\n`;
            output += JSON.stringify(orderedFlow, null, 2) + "\n";
            output += `_.:._ END_FLOW _.:._\n\n`;
        }

        output += `--- FOUND ${Object.keys(advancedFlows).length} ADVANCED FLOWS ---\n\n`;

        // Output Advanced Flows
        for (const id in advancedFlows) {
            const aflow = advancedFlows[id];
            
            const folderName = getFolderPath(aflow.folder);
            const orderedAflow = { type: "advanced", folderName: folderName, ...aflow };
            
            const safeFolderName = folderName.replace(/[/\\?%*:|"<>]/g, '-');
            const safeFlowName = aflow.name.replace(/[/\\?%*:|"<>]/g, '-');
            
            output += `_.:._ START_ADVANCED_FLOW: [${safeFolderName}] ${safeFlowName} (${aflow.id}) _.:._\n`;
            output += JSON.stringify(orderedAflow, null, 2) + "\n";
            output += `_.:._ END_ADVANCED_FLOW _.:._\n\n`;
        }
        
        output += "--- EXPORT COMPLETE ---";
        
        // --- AUTO DOWNLOAD MECHANISM ---
        const blob = new Blob([output], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `all_flows.txt`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
        
        return "File download triggered successfully!";
        
    } catch (error) {
        return "Error fetching flows: " + error.message;
    }
}

exportHomeyFlows();

```

* * *

## Split Script : Long Name

```auto
import json
import os
import re

input_file = "all_flows.txt"

if not os.path.exists(input_file):
    print(f"Error: {input_file} not found.")
    exit(1)

STD_DIR = "Standard_Flows"
ADV_DIR = "Advanced_Flows"

os.makedirs(STD_DIR, exist_ok=True)
os.makedirs(ADV_DIR, exist_ok=True)

with open(input_file, "r", encoding="utf-8") as f:
    content = f.read()

content = content.replace("_.:._ START_ADVANCED_FLOW: ", "_.:._ START_FLOW: ")
blocks = content.split("_.:._ START_FLOW: ")
count = 0

# Matches: [Folder] Name (ID)
header_pattern = re.compile(r"^\[(.*?)\]\s+(.*?)\s+\(([0-9a-fA-F-]+)\)$")

for block in blocks:
    if "_.:._" not in block:
        continue
        
    header, _, body = block.partition("\n")
    raw_header = header.replace("_.:._", "").strip()
    
    json_str, _, _ = body.partition("_.:._")
    json_str = json_str.strip()
    
    if not json_str:
        continue
        
    try:
        flow_data = json.loads(json_str)
        flow_type = flow_data.get("type", "unknown")
        
        # Use the uncorrupted hierarchical path stored inside the JSON object
        folder_name = flow_data.get("folderName", "Root")
        
        match = header_pattern.match(raw_header)
        if match:
            _, flow_name, flow_id = match.groups()
        else:
            flow_name = raw_header
            flow_id = flow_data.get("id", "unknown")
        
        if flow_type == "standard":
            base_dir = STD_DIR
            extension = ".homeystdflow"
        elif flow_type == "advanced":
            base_dir = ADV_DIR
            extension = ".homeyadvflow"
        else:
            base_dir = "."
            extension = ".json"
            
        # Split the hierarchical path by " / " to build true nested subfolders
        folder_parts = folder_name.split(" / ")
        clean_parts = [
            "".join(c if c.isalnum() or c in "._-" else "_" for c in part).strip("_")
            for part in folder_parts
        ]
        clean_parts = [p if p else "Root" for p in clean_parts]
        
        # Build the nested directory path safely
        target_dir = os.path.join(base_dir, *clean_parts)
        os.makedirs(target_dir, exist_ok=True)
        
        clean_name = "".join(c if c.isalnum() or c in "._-" else "_" for c in flow_name).strip("_")
        
        # Base filename using name
        filename = os.path.join(target_dir, f"{clean_name}_{flow_id}{extension}")
        
        # Fallback conflict resolution: if filename exists, append incremental counter
        counter = 1
        base_filename = filename
        while os.path.exists(filename):
            counter += 1
            name_part, ext_part = os.path.splitext(base_filename)
            filename = f"{name_part}_{counter}{ext_part}"
        
        with open(filename, "w", encoding="utf-8") as out_f:
            json.dump(flow_data, out_f, indent=2, ensure_ascii=False)
            
        print(f"Saved: {filename}")
        count += 1
    except json.JSONDecodeError as e:
        print(f"Failed to parse flow '{raw_header}': {e}")
    except Exception as e:
        continue

print(f"\nDone! Successfully processed {count} flows.")

```

* * *

## Split Script : Short Name

```auto
import json
import os
import re

input_file = "all_flows.txt"

if not os.path.exists(input_file):
    print(f"Error: {input_file} not found.")
    exit(1)

STD_DIR = "Standard_Flows"
ADV_DIR = "Advanced_Flows"

os.makedirs(STD_DIR, exist_ok=True)
os.makedirs(ADV_DIR, exist_ok=True)

with open(input_file, "r", encoding="utf-8") as f:
    content = f.read()

content = content.replace("_.:._ START_ADVANCED_FLOW: ", "_.:._ START_FLOW: ")
blocks = content.split("_.:._ START_FLOW: ")
count = 0

# Matches: [Folder] Name (ID)
header_pattern = re.compile(r"^\[(.*?)\]\s+(.*?)\s+\(([0-9a-fA-F-]+)\)$")

for block in blocks:
    if "_.:._" not in block:
        continue
        
    header, _, body = block.partition("\n")
    raw_header = header.replace("_.:._", "").strip()
    
    json_str, _, _ = body.partition("_.:._")
    json_str = json_str.strip()
    
    if not json_str:
        continue
        
    try:
        flow_data = json.loads(json_str)
        flow_type = flow_data.get("type", "unknown")
        
        # Use the uncorrupted hierarchical path stored inside the JSON object
        folder_name = flow_data.get("folderName", "Root")
        
        match = header_pattern.match(raw_header)
        if match:
            _, flow_name, flow_id = match.groups()
        else:
            flow_name = raw_header
            flow_id = flow_data.get("id", "unknown")
        
        if flow_type == "standard":
            base_dir = STD_DIR
            extension = ".homeystdflow"
        elif flow_type == "advanced":
            base_dir = ADV_DIR
            extension = ".homeyadvflow"
        else:
            base_dir = "."
            extension = ".json"
            
        # Split the hierarchical path by " / " to build true nested subfolders
        folder_parts = folder_name.split(" / ")
        clean_parts = [
            "".join(c if c.isalnum() or c in "._-" else "_" for c in part).strip("_")
            for part in folder_parts
        ]
        clean_parts = [p if p else "Root" for p in clean_parts]
        
        # Build the nested directory path safely
        target_dir = os.path.join(base_dir, *clean_parts)
        os.makedirs(target_dir, exist_ok=True)
        
        clean_name = "".join(c if c.isalnum() or c in "._-" else "_" for c in flow_name).strip("_")
        
        # Base filename using name
        filename = os.path.join(target_dir, f"{clean_name}{extension}")
        
        # Fallback conflict resolution: if filename exists, append incremental counter
        counter = 1
        base_filename = filename
        while os.path.exists(filename):
            counter += 1
            name_part, ext_part = os.path.splitext(base_filename)
            filename = f"{name_part}_{counter}{ext_part}"
        
        with open(filename, "w", encoding="utf-8") as out_f:
            json.dump(flow_data, out_f, indent=2, ensure_ascii=False)
            
        print(f"Saved: {filename}")
        count += 1
    except json.JSONDecodeError as e:
        print(f"Failed to parse flow '{raw_header}': {e}")
    except Exception as e:
        continue

print(f"\nDone! Successfully processed {count} flows.")

```

* * *

---

<div class="post-metadata">

**Author:** ![Mike1233](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/mike1233/32/20991_2.png) [@Mike1233](https://community.homey.app/u/Mike1233)\
**Post date:** [September 3, 2026, 10:50pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/2 "2026-09-03T22:50:29Z")

</div>

I do not understand much of scripts so i have ChatGPT checked it. And she thinks it should work.  
But is it that flows with the same name overwrite each other the way this script works?  
Her idea was to add the flow ID to the flow name so the same flow names is no problem.

Thanks for your work.

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 3, 2026, 11:00pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/3 "2026-09-03T23:00:50Z")

</div>

Fixed:  
[https://community.homey.app/t/backup-script-all-flows-and-split-them/159140](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140)

---

<div class="post-metadata">

**Author:** ![smarthomesven](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/smarthomesven/32/79712_2.png) [@smarthomesven](https://community.homey.app/u/smarthomesven)\
**Post date:** [September 3, 2026, 11:01pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/4 "2026-09-03T23:01:31Z")

</div>

The app Flow Version History might be useful as well, it does this automatically and keeps multiple revisions

> **[Flow Version History | Homey](https://homey.app/en-us/app/nl.dypodex.versionhistory/Flow-Version-History/)**
>
> Made a mistake? No problem!

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 3, 2026, 11:07pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/5 "2026-09-03T23:07:20Z")

</div>

But this script get all flows at the same time, and split them in to different folders/files. You app is savin one by one. Just like a normal export.

But please use this to make an app that get all flows and split them and save them

---

<div class="post-metadata">

**Author:** ![Mike1233](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/mike1233/32/20991_2.png) [@Mike1233](https://community.homey.app/u/Mike1233)\
**Post date:** [September 3, 2026, 11:11pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/6 "2026-09-03T23:11:10Z")

</div>

> [@Mike\_Nono](#):
>
> This will overwrite if you have a file with the same name yes.
> 
> The output file will be like this.
> 
> Name of the flow in homey ex:  
> An advanced flow named “Adv\_Sensors” then the file will be named “Adv\_Sensors.homeyadvflow”

So if i have multiple flows with the same name, let’s say “turn on light”, in different folders, at the end of the backup i only have one flow left with the name “turn on light”.

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 3, 2026, 11:22pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/7 "2026-09-03T23:22:05Z")

</div>

Fixed:  
[https://community.homey.app/t/backup-script-all-flows-and-split-them/159140](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140)

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 4, 2026, 8:35am UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/11 "2026-09-04T08:35:04Z")

</div>

@Mike-Nono: I would love to have a script that can save all my scripts as javascript text files. Think it’s possible?

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 4, 2026, 9:11am UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/12 "2026-09-04T09:11:09Z")

</div>

**Homey-Script Backup:** It has it’s own thread

[https://community.homey.app/t/backup-script-for-homey-script/159157](https://community.homey.app/t/backup-script-for-homey-script/159157)

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 4, 2026, 2:03pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/13 "2026-09-04T14:03:43Z")

</div>

Tried a restore of some flows: working. 👍

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 5, 2026, 2:06pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/14 "2026-09-05T14:06:57Z")

</div>

FlowSplit skips some flows because of error: failed to parse flow: … : Unterminated string starting at: …  
Happens a few times. Have to dig into it.

Cause is: in some code I use === or === END too.

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 5, 2026, 3:31pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/15 "2026-09-05T15:31:33Z")

</div>

Fixed:  
[https://community.homey.app/t/backup-script-all-flows-and-split-them/159140](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140)

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 5, 2026, 7:06pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/16 "2026-09-05T19:06:44Z")

</div>

@SingKT  
Did some change to the script, so it will not collide if using “===” in the flow like you do. Test it please

[https://community.homey.app/t/backup-script-all-flows-and-split-them](https://community.homey.app/t/backup-script-all-flows-and-split-them)

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 5, 2026, 9:50pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/17 "2026-09-05T21:50:52Z")

</div>

Looks good. Error is gone.

---

<div class="post-metadata">

**Author:** ![Mike\_Nono](https://avatars.discourse-cdn.com/v4/letter/m/7feea3/32.png) [@Mike\_Nono](https://community.homey.app/u/Mike_Nono)\
**Post date:** [September 5, 2026, 10:01pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/18 "2026-09-05T22:01:37Z")

</div>

Great.  
If all is running ok and no error.

That’s what we all like.

Thanks 🙂

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 6, 2026, 2:46pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/19 "2026-09-06T14:46:12Z")

</div>

Was trying to restore some flows. One flow has an invalid card e.g. is broken thus can’t be restored. So I wonder if your backup script could check bevorehand on broken flows. The interesting thing is some broken flows can be restored. Seems only flows where a refered app is missing might not restore. Haven’t digged in deaper yet.

---

<div class="post-metadata">

**Author:** ![SunBeech](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/sunbeech/32/111391_2.png) [@SunBeech](https://community.homey.app/u/SunBeech)\
**Post date:** [September 6, 2026, 3:00pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/20 "2026-09-06T15:00:04Z")

</div>

If not in use already, you might want to use the [Flow Checker](https://homey.app/a/com.athom.flowchecker/) app that runs regular scans on your flows to detect broken flows (e.g. due to missing apps/devices and missing tags/variables).

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 6, 2026, 4:19pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/21 "2026-09-06T16:19:41Z")

</div>

Am using flow checker but I do prefer when my backup warns me when backing up something faulty.  
Sorry, may sound harsher then intended.

---

<div class="post-metadata">

**Author:** ![SunBeech](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/sunbeech/32/111391_2.png) [@SunBeech](https://community.homey.app/u/SunBeech)\
**Post date:** [September 6, 2026, 4:23pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/22 "2026-09-06T16:23:26Z")

</div>

No worries 😉. I understand your point: an extra notification can be helpful.

Just for my info: I thought you could also import flows with missing apps/devices/tags/variables. Those elements are then shown as ‘unavailable’ in the imported flow. At least that was the way with manually exported flows via the ALT+Right Click method. Is that no longer the case?

---

<div class="post-metadata">

**Author:** ![SingKT](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/singkt/32/173226_2.png) [@SingKT](https://community.homey.app/u/SingKT)\
**Post date:** [September 6, 2026, 4:54pm UTC](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140/23 "2026-09-06T16:54:27Z")

</div>

Yes, some of my broken flows I could import. The one not working was probably using a flowcard from a deleted app.

[Next page](https://community.homey.app/t/backup-script-all-flows-and-split-them/159140.md?page=2)
