# Unzip

Action `arrow.up.bin`

`action.unzip`

Extract a `.zip` archive to real files on disk, then send those files downstream. The archive path comes from the upstream payload (typically `{{filePath}}` from an Input or Directory Changed trigger). A zip of 200 files is just a document of 200 pages — Unzip fans out over the contents so each file flows through the rest of your workflow on its own.

## Two outlets, one extraction

A single extraction feeds both outlets — wire whichever fits whether you want one result for the whole archive or one result per file. (Same pattern as Extract Text (PDF)’s Document/Page split.)

Contents

Emits once with the whole archive — the `destination` directory, a `files` array of every extracted path, and `fileCount`. Use it for one action about the whole extraction (e.g. “notify me it’s done”).

File

Fans out — runs everything downstream once per extracted file, each run carrying that file’s `filePath`, `fileName`, and `index`. Use it to route each file by type — image → OCR/vision, text → AI, and so on.

## Ports

Inlet
Input (file)
Outlets
Contents (any)
File (any, fans out)

## Configuration

Field | Type | Default | Description
--- | --- | --- | ---
archivePath | Variable picker | `{{filePath}}` | The `.zip` to extract. Pick the upstream variable that holds the path (a bare key, a `{{key}}` template, or a literal path all work); blank falls back to `filePath`.
destination | Text | `{{directory}}/{{fileName}}` | Where to extract. Defaults to a sibling folder next to the archive — this keeps the output out of a watched directory so it doesn’t re-trigger the workflow.
overwrite | Dropdown | `overwrite` | On a name collision at the destination: `Overwrite`, `Keep both (suffix)`, or `Skip existing`.

## Output Variables

Merged onto the incoming payload (so upstream keys are preserved). Which keys you get depends on the outlet you wired.

Variable | Outlet | Description
--- | --- | ---
destination | Contents | Absolute path to the extraction directory.
files | Contents | Array of `file` refs for every extracted file.
fileCount | Contents | Number of files extracted.
filePath | File | This file’s absolute path (drops straight into OCR, PDF, AI, Move…).
fileName | File | This file’s name.
index | File | This file’s 0-based position in the archive.
success | Both | `true` on success; `false` on a graceful failure (see below).

## Error handling

Two tiers, by design. Everyday problems fail gracefully so a workflow can branch on them; only genuine safety violations hard-fail.

Graceful — `success: false`

Returns a normal payload with `success: false` and an `error` reason — never throws, so the run continues and a Condition can route the failure.

- `no_archive_path` — no archive path was provided or resolved.
- `archive_not_found` — the path is valid but no file exists there.
- `not_a_zip` — the file isn’t a readable archive (ditto exits non-zero). This is the “input isn’t an archive” case — it fails gracefully and emits a single `success:false` payload (not the per-file fan-out).

Hard fail — stops the run

Throws and marks the node failed (the red banner) only for real safety violations:

- `protected location` — the archive or destination resolves into a denied path (`~/Library` internals, `~/.ssh`, system roots) or uses `..` traversal. iCloud Drive and other cloud folders are allowed.
- `zip-slip / symlink` — an entry inside the archive tries to escape the extraction directory or is a symlink. The whole extraction is discarded before anything reaches your destination.

## Security

Extraction lands in a private temporary folder first. Every entry is checked to stay inside that folder and symlink entries are rejected, so a malicious archive (zip-slip) is caught and discarded before anything reaches your real destination. Archive and destination paths are validated against the same protected-location rules as Move File; iCloud Drive and other cloud folders are allowed.

## Example

Describe every image in a zip: extract, then fan out the File outlet to OCR + an AI vision model.