# BetterZip Format Definitions

BetterZip uses JSON files to describe archive formats. Built-in formats ship inside the app; you can add your own through the Helpers tab in Preferences. Each JSON file defines how BetterZip lists, extracts, compresses, and tests a particular archive type.

This document explains every field you can use in a format definition.


## Minimal Example

A read-only format that can only be opened (not created):

```json
{
  "class": "xad",
  "format": "sit",
  "name": "StuffIt Archive",
  "tag": 110,
  "extensions": ["sit", "sitx"]
}
```


## Full Example

A format that supports creating, extracting, testing, and splitting:

```json
{
  "class": "tar",
  "format": "tzst",
  "name": "TAR Format, Zstandard compressed (tzst)",
  "tag": 9,
  "builtIn": true,
  "installPath": "$BUNDLE_HELPERS/zstd",
  "canSave": true,
  "canUpdate": false,

  "magicBytes": [
    {
      "start": 0,
      "hex": true,
      "bytes": "28B52FFD"
    }
  ],

  "extensions": ["tzst", "zst"],

  "extract": {
    "weight": 14
  },

  "compress": {
    "factors": [
      {
        "standardTitleTag": 1,
        "position": 1,
        "weight": 5,
        "options": ["-1"]
      },
      {
        "standardTitleTag": 2,
        "position": 2,
        "weight": 6,
        "options": []
      },
      {
        "standardTitleTag": 3,
        "position": 4,
        "weight": 75,
        "options": ["-19"]
      }
    ]
  }
}
```


## Top-Level Fields

### Identity

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `class` | String | Yes | Determines which handler processes this format. See [Handler Classes](#handler-classes). |
| `format` | String | Yes | Internal format identifier passed to command-line tools (e.g. `"zip"`, `"7z"`, `"tgz"`). |
| `name` | String | Yes | Display name shown in menus and the save panel. |
| `tag` | Integer | Yes | Unique numeric identifier. Used internally to reference formats in presets and scripts. Pick a number not already in use. |
| `builtIn` | Boolean | No | `true` for formats that ship with BetterZip. Leave out or set to `false` for your own definitions. |
| `comment` | String | No | Freeform note or URL for your own reference. Not shown in the UI. |
| `iconName` | String | No | Name of a custom icon (without extension) to display for this format. |


### Capabilities

All of these default to `false` if omitted.

| Field | Type | Description |
|-------|------|-------------|
| `canSave` | Boolean | Can BetterZip create archives in this format? |
| `canUpdate` | Boolean | Can files be added to or removed from an existing archive? |
| `canTest` | Boolean | Can archive integrity be verified? |
| `canSplit` | Boolean | Can archives be split into multiple volumes? |
| `canWriteComments` | Boolean | Can the archive store a text comment? |
| `canFilterRsrc` | Boolean | Can macOS resource fork files (`._*`, `__MACOSX`) be filtered out? Defaults to `true` unless explicitly set to `false`. |
| `canSaveWithForks` | Boolean | Can the format preserve macOS resource forks natively? |
| `canOnlyCompressFolders` | Boolean | Set to `true` if the format can only compress entire folders, not individual files (e.g. disk images). |
| `numberedParts` | Boolean | Enables the `name.1.ext`, `name.2.ext`, ... multi-volume naming scheme for this format. Only set this if the format's tools actually produce this pattern. Most formats use other split naming conventions (`.001`, `.z01`, `.part001.rar`) that are detected automatically. |


### Format Behavior

These fields configure format-specific behavior. All are optional.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `rsrcFolder` | String | `null` | Name of the resource fork folder inside archives (e.g. `"__MACOSX"`). |
| `defaultExtension` | String | from `format` | Override for the default file extension when creating new archives. |
| `encryptionDetection` | Array | `[]` | Patterns for detecting encryption from 7z listing output. See [Encryption Detection](#encryption-detection). |
| `threadOption` | String | `null` | Template for the tool's thread-count flag, using the `$THREADS` placeholder (e.g. `"-mmt=$THREADS"`, `"--threads=$THREADS"`). Applied during extraction and other non-compression operations. For compression, `$THREADS` is typically placed inside factor `options` arrays instead. |


### Tool Paths

These tell BetterZip which command-line tool to invoke.

| Field | Type | Description |
|-------|------|-------------|
| `installPath` | String | Path to the tool used for all operations. |
| `installPathList` | String | Override for listing only. Falls back to `installPath`. |
| `installPathExtract` | String | Override for extraction only. Falls back to `installPath`. |
| `installPathCompress` | String | Override for compression only. Falls back to `installPath`. |

**Path variables** you can use:

| Variable | Expands to |
|----------|------------|
| `$BUNDLE_HELPERS` | The Helpers folder inside the BetterZip app bundle |
| `$APP_SUPPORT` | `~/Library/Application Support/com.macitbetter.betterzip` |

If the tool is installed in a standard location you can use an absolute path like `/usr/local/bin/lz4`.


## File Type Detection

BetterZip identifies archive types in two steps: first by checking magic bytes at specific offsets in the file, then by falling back to the filename extension.

### `magicBytes`

An array of byte patterns to check. If any pattern matches, the file is recognized as this format.

```json
"magicBytes": [
  {
    "start": 0,
    "hex": true,
    "bytes": "504B0304"
  }
]
```

| Field | Type | Description |
|-------|------|-------------|
| `start` | Integer | Byte offset in the file where the check begins. |
| `hex` | Boolean | `true` if `bytes` is a hex string, `false` if it is plain ASCII. |
| `bytes` | String | The byte sequence to match. Hex example: `"504B0304"` (ZIP signature). ASCII example: `"ustar"` (TAR). |

### `extensions`

An array of filename extensions (without the dot) that identify this format:

```json
"extensions": ["zip", "jar", "war", "cbz"]
```


## Operation Dictionaries

Format-specific settings for listing, extracting, compressing, and testing are grouped under four optional top-level keys: `list`, `extract`, `compress`, and `test`. Only include the ones you need.

### `extract`

```json
"extract": {
  "weight": 17
}
```

| Field | Type | Description |
|-------|------|-------------|
| `weight` | Number | Relative extraction speed. Higher values mean slower extraction. Used to estimate progress. If omitted, defaults to 28. |

### `compress`

Contains compression levels, encryption options, and an optional label override.

```json
"compress": {
  "compressionTitle": "Compression:",
  "factors": [ ... ],
  "encryptionMethods": [ ... ]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `compressionTitle` | String | Label shown above the compression level popup in the save panel. Defaults to "Compression:" if omitted. Use a custom label when the choices are not traditional compression levels (e.g. "Image type/compression" for DMG). |
| `factors` | Array | Compression level presets. See [Compression Factors](#compression-factors). |
| `encryptionMethods` | Array | Encryption options. See [Encryption Methods](#encryption-methods). |

### `list`


### `test`

These are reserved for user-defined formats (class `"user"`) that need custom command-line options for testing. See [User-Defined Formats](#user-defined-formats).


## Progress Reporting

While an external tool runs, BetterZip scans its live output for a percent value to drive the progress bar. The pattern is configurable with the optional `progress` object, either at the top level or inside an operation dictionary (`list`, `extract`, `compress`, `test`). The most specific one wins: operation dictionary first, then top level, then the built-in default.

```json
"progress": {
  "regex": "^\\s*(\\d{1,3})%"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `regex` | String | Regular expression applied to each chunk of tool output. Capture group 1 must yield the percent value (0–100). An empty string disables percent scanning for tools that print no usable progress. |

The built-in default is `^\s*(\d{1,3})%`, matching the percent output of the bundled `7za` tool.

While the tool runs, entries of the `errors` array with `"standardError": "permission"` are also matched live against each output chunk, so a permission error cancels the operation immediately instead of after the tool exits.


## Compression Factors

Each entry in the `factors` array represents one compression level shown in the save panel dropdown.

```json
{
  "standardTitleTag": 2,
  "position": 3,
  "weight": 12,
  "options": ["-mx=5"]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `standardTitleTag` | Integer | References a built-in label. See the table below. Use this when a standard label fits. |
| `title` | String | Custom label text. Use instead of `standardTitleTag` when you need a non-standard name (e.g. `"Read/write image"`). |
| `tag` | Integer | Numeric identifier for this level. Required when using a custom `title` instead of `standardTitleTag`. |
| `position` | Integer | Display order in the popup. `1` = first, `2` = second, etc. |
| `weight` | Number | Relative compression speed. Higher = slower. Used for progress estimation. |
| `options` | Array | Command-line flags passed to the compression tool for this level. |

### Standard Compression Tags

| Tag | Label |
|-----|-------|
| 0 | No compression |
| 1 | Fast, but larger archives |
| 2 | Normal |
| 3 | Best, but slower operation |
| 4 | High, but slower operation |
| 5 | Best (solid, slow extraction) |

These labels are automatically localized into all supported languages.


## Encryption Methods

Each entry in the `encryptionMethods` array represents one encryption option in the save panel.

```json
{
  "standardTitleTag": 2,
  "position": 2,
  "options": ["-mem=AES256", "-p$PASSWORD"]
}
```

The fields are the same as for compression factors. Use `$PASSWORD` as a placeholder in `options` – BetterZip replaces it with the actual password at runtime.

### Standard Encryption Tags

| Tag | Label |
|-----|-------|
| 0 | Not encrypted |
| 1 | Weak (PKZip2 compatible) |
| 2 | Strong (AES-256) |
| 3 | Strong (AES-256) with header encryption |
| 4 | macOS default (AES-128) |


## Handler Classes

The `class` field determines which handler processes the format. Each handler has different capabilities.

| Class | Description | Mechanism |
|-------|-------------|-----------|
| `tar` | TAR-based formats (tar, tgz, tbz, txz, tzst, tbr). Can pipe through an external compressor. | Shells out to `gnutar` + compressor |
| `zip` | ZIP archives. | Shells out to `7za` |
| `7Zip` | 7-Zip format (7z). | Shells out to `7za` |
| `7ZipBase` | Other formats handled by 7za (CAB, NSIS, WIM, ARJ, CHM). | Shells out to `7za` |
| `split` | Split archive files (.001, .002, ...). | Shells out to `7za` |
| `rar` | RAR archives (read-only unless RAR command-line tools are installed). | In-process via unrar library |
| `xar` | XAR, XIP, PKG archives. | In-process via xar library |
| `hdi` | Apple disk images (DMG, ISO). | Shells out to `/usr/bin/hdiutil` |
| `xad` | Read-only formats handled by the XADMaster framework (SIT, PDF, HQX, many legacy formats). | In-process via XADMaster.framework |
| `mime` | TNEF / winmail.dat. | In-process via TNEF.framework |
| `user` | Fully user-configurable. All operations are defined through the JSON. | Shells out to specified tool |

The classes `zip`, `7Zip`, `7ZipBase`, and `split` all use the same unified handler internally. Behavioral differences (encryption, multi-part handling, etc.) are configured through the JSON fields.

### 7-Zip Base Defaults (`7ZipBase.json`)

All formats handled by the 7za tool (`zip`, `7Zip`, `7ZipBase`, `split`) automatically inherit shared defaults from `7ZipBase.json`. This base config provides settings that are common to all 7za-based formats:

- **`installPath`** – path to the `7za` binary
- **`canTest`** – all 7za formats support integrity testing
- **`errors`** – standard error patterns for password prompts, broken archives, and permission errors
- **`progress`** – the pattern for scanning 7za's percent output, see [Progress Reporting](#progress-reporting)

Individual format definitions override any field they specify – the base config only fills in missing values. 

You can edit the base config through BetterZip > Settings > Helpers (it appears as "7-Zip Base Defaults"). Changes apply to all 7za-based formats that do not override the changed field. This is especially useful for adding custom error patterns that should apply across all 7za formats.

For most custom formats, use class `"tar"` (if your format is tar-based with a compressor) or `"user"` (for anything else).


## User-Defined Formats

When using class `"user"`, you have full control over the command-line options for every operation.

### Operation Options

The `list`, `extract`, `compress`, and `test` dictionaries can each contain:

| Field | Type | Description |
|-------|------|-------------|
| `options` | Array | Command-line arguments passed to the tool. Supports placeholder substitution (see below). |
| `installPath` | String | Override for this operation only. Falls back to `installPath` on top level. |
| `stdioPassword` | Boolean | If `true`, the password is piped to the tool via stdin instead of passed as a command-line argument. When set, `$PASSWORD` placeholders in `options` are not expanded. Can also be set at the top level as a default for all operations. |

### Security and shell safety

`options` values are passed to the tool as command-line fragments. Use only real flags, values, and the documented placeholders (`$THREADS`, `$FACTOR`, `$PASSWORD`, …). Do **not** put shell metacharacters – ``` ` ``` `$` `;` `|` `&` `<` `>` `(` `)` `"` `'` `\` – into an option value.

The TAR-family handlers (`tgz`, `txz`, `tzst`, `tbz`, `tbr`, and `tar` with a custom compressor) build a shell pipeline. To prevent command injection, any flag/option value that still contains a shell metacharacter after placeholder expansion is **rejected at runtime**: the operation is aborted and the offending value is logged, rather than executed. Legitimate options (`-c9`, `--threads=4`, `-mx=9`) never trigger this.

Tool paths (`installPath`) and the file names inside the archive are quoted automatically, so they may safely contain spaces, quotes, and other special characters – you do not need to (and should not) quote them yourself.

### Placeholders

Placeholders in option strings are substituted at runtime. Available placeholders depend on the operation.

#### Common Placeholders (all operations)

| Placeholder | Description |
|-------------|-------------|
| `$ARCHIVE` | Full path to the archive file. Must be a standalone option string (not embedded in other text). |
| `$PASSWORD` | The archive password. Can be embedded in an option string (e.g. `"-p$PASSWORD"`). Not expanded when `stdioPassword` is `true`. |
| `$THREADS` | Replaced with the user's thread-count preference (from Preferences > Advanced). Can be embedded in an option string (e.g. `"-mmt=$THREADS"`, `"--threads=$THREADS"`, `"-p$THREADS"`). When the preference is set to Auto (0), the entire option containing `$THREADS` is **dropped** – the tool uses its own default. |

#### Extract Placeholders

| Placeholder | Description |
|-------------|-------------|
| `$LISTFILE` | Path to a temporary file containing the list of items to extract (one per line). The placeholder is replaced inline within the option string (e.g. `"@$LISTFILE"`). |
| `$FILES` | Special repeating placeholder. The option string containing `$FILES` is split by spaces. The resulting group of arguments is repeated once per file to extract: in each repetition, the component containing `$FILES` is replaced with the file path, and other components are passed through unchanged. Example: `"-g $FILES"` with files `a.txt` and `b.txt` produces `-g a.txt -g b.txt`. |

#### Compress Placeholders

| Placeholder | Description |
|-------------|-------------|
| `$FACTOR` | Replaced with the `options` array of the selected compression factor (looked up by tag from `factors`). |
| `$ENCRYPTION` | Replaced with the `options` array of the selected encryption method (looked up by tag from `encryptionMethods`). Only expanded when a password is set. `$PASSWORD` inside encryption options is also expanded. |
| `$SPLIT` | Replaced with the `splitOption` string (see below), with `$VOLUME_SIZE` substituted with the calculated volume size in bytes. Only expanded when the user has configured a volume size. |
| `$LISTFILE` | Same as in extract. |
| `$FILES` | Same repeating behavior as in extract. When no specific files are given but a folder is being compressed, `$FILES` is replaced with `"."`. |

### Compress-Specific Fields

| Field | Type | Description |
|-------|------|-------------|
| `splitOption` | String | Template for volume splitting. Must contain `$VOLUME_SIZE` which is replaced with the volume size in bytes. Spaces split this into separate arguments. Example: `"-s $VOLUME_SIZE"` becomes `["-s", "1048576"]`. |
| `compressionTitle` | String | Custom label shown above the compression level popup in the save panel. Defaults to "Compression:" if omitted. |

### List-Specific Fields

| Field | Type | Description |
|-------|------|-------------|
| `headerLines` | Integer | Number of lines to skip at the start of the tool's output before parsing items. |
| `footerLines` | Integer | Number of lines to skip at the end of the tool's output. |
| `parser` | Object | Defines how to parse the tool's output into archive items. Contains `regex` and `template`. |
| `parser.regex` | String | Regular expression with capture groups applied to each output line. |
| `parser.template` | String | Tab-separated key=value template using `$1`, `$2`, etc. for capture group references. Supported keys: `path`, `attr`, `unco` (uncompressed size), `comp` (compressed size), `time`. |

Example:

```json
"list": {
  "headerLines": 2,
  "footerLines": 0,
  "options": ["-l", "$ARCHIVE"],
  "parser": {
    "regex": "(?:\\[[^\\]]*\\])+\\s*([-dlrwxsStT]{10})\\s+\\S+\\t\\S+\\t(\\d+)\\s+(\\S+)\\t([^\\t]+)\\t(.+)",
    "template": "path=$5\tattr=$1\tunco=$2$3\tcomp=0\ttime=$4"
  }
},
"extract": {
  "weight": 18,
  "options": ["-x", "$ARCHIVE", "-g $FILES"]
},
"compress": {
  "splitOption": "-s $VOLUME_SIZE",
  "options": ["-c", "$ARCHIVE", "$FACTOR", "$ENCRYPTION", "$SPLIT", "-g $FILES"],
  "factors": [ ... ],
  "encryptionMethods": [ ... ]
},
"test": {
  "options": ["-t", "$ARCHIVE"]
}
```

### Error Handling

User-defined formats can include an `errors` array to map tool output to meaningful error messages. The `errors` array can appear in two places:

- **Top level** – applies to all operations (list, extract, test, compress).
- **Inside an operation dictionary** (`list`, `extract`, `test`, `compress`) – applies only to that operation.

If both exist, the operation-specific `errors` array is used exclusively – the two arrays are not combined.

```json
"errors": [
  {
    "recognize": ["Wrong password"],
    "standardError": "password",
  },
  {
    "recognize": ["Sub items Errors", "Headers Error", "ERRORS:"],
    "standardError": "broken"
  },
  {
    "recognize": "ERROR: Operation not permitted",
    "standardError": "permission"
  },
  {
    "recognize": "ERROR: Need more memory",
    "userMessage": "The operation needs more memory to succeed.",
    "continue": false
  }
]
```

| Field | Type | Description |
|-------|------|-------------|
| `recognize` | String or Array | One or more strings to look for in the tool's output. If any string is found, this error rule matches. |
| `standardError` | String | Standard error conditions: `"password"`, `"broken"`, or `"permission"`. |
| `userMessage` | String | When this is not a standard error condition, this message is shown to the user. |
| `continue` | Boolean | If `true`, the alert panel with the userMessage has a continue button and the operation can continue after displaying the message. |


## Encryption Detection

The `encryptionDetection` array tells BetterZip how to detect encryption from 7z listing output. Each entry matches a pattern in the compression method string reported by 7za.

```json
"encryptionDetection": [
  { "pattern": "ZipCrypto", "method": 1 },
  { "pattern": "AES", "method": 2 }
]
```

| Field | Type | Description |
|-------|------|-------------|
| `pattern` | String | Substring to look for in the item's method string. |
| `method` | Integer | Encryption method value: `1` = weak, `2` = strong. |
| `headerEncryptionMethod` | Integer | If present, this value is used instead of `method` when the archive listing itself is encrypted (header encryption). Typically `3`. |

For 7z archives with optional header encryption:

```json
"encryptionDetection": [
  { "pattern": "7zAES", "method": 2, "headerEncryptionMethod": 3 }
]
```


## Multi-Volume Naming Schemes

BetterZip automatically detects multi-volume archives using these naming patterns:

| Scheme | Pattern | First Volume | Handler |
|--------|---------|-------------|---------|
| Generic split | `name.ext.001`, `.002`, ... | `.001` | Detected by numeric extension; scans `.001`–`.999` |
| Numbered parts | `name.1.ext`, `name.2.ext`, ... | `name.1.ext` | Only for formats with `"numberedParts": true` |
| New RAR | `name.part001.rar`, `.part002.rar`, ... | `.part001.rar` | Detected by `part` prefix in second-to-last extension |
| Old RAR | `name.rar`, `name.r01`–`.r99`, `.s00`–`.s99`, `.t00`–`.t99` | `.rar` | Detected by `[r-t]##` extension pattern |
| WinZip | `name.z01`–`.z99`, `name.zip` | `.z01` | Detected by `z##` extension pattern; `.zip` is the final part |

When a user opens a later volume, BetterZip automatically redirects to the first volume and scans for all parts.

The `numberedParts` scheme requires an opt-in flag because `name.1.ext` patterns are common in non-archive files (e.g. `report.1.pdf`). Only enable it for formats whose tools actually produce this naming convention.


## Tips

- **Tag numbers**: Pick a tag number that is not already used. Tag numbers below 1000 are reserved. Use numbers above for custom formats.
- **Testing your format**: After adding a JSON file in BetterZip > Settings > Helpers, your format should be recognized when opening archives and it should appear in the save panel's and save preset configuration's format drop-down (if `canSave` is `true`).
- **Compression factors**: You need at least one entry in `factors` if your format supports saving. If the format has only one compression level, include a single entry.
- **Magic bytes**: If you do not know the magic bytes for your format, leave the array empty and rely on file extensions only.
- **Tool path**: Make sure the tool is installed and the path is correct. BetterZip will show the format as unavailable if it cannot find the tool.
