---
name: uploads
description: Multi-file upload — web, CLI, and the binary content branch.
---

# Uploads

Anchorify has two multi-file upload surfaces: the web upload page and the CLI `upload-folder` subcommand. Both let an admin publish a batch of files to a single project with one visibility tier.

## Web — `/<org>/<project>/upload`

Admin-only. The page is a file picker (`<input type=file multiple>`) plus a visibility radio that applies to the whole batch. Submit POSTs the multipart form to the same path.

Behavior per file:

- **Size limit**: 1 MiB (`MAX_SHARE_BYTES`). Oversize files are rejected with a per-row error; the rest of the batch continues.
- **Slug**: kebab-cased basename. `Q1 Report.md` → `q1-report`. Slug collisions auto-suffix `-2`, `-3`, … up to 50 attempts.
- **Content type**: filename extension (see [content types](/docs/content-types)).
- **Binary detection**: a NUL byte in the first 1KB routes the file to the binary path — bytes go to R2 storage under `shares/<share_id>/<filename>` and the share's content column gets the sentinel `"@@blob"`. The content type is `image`, `pdf`, or `binary` based on MIME + extension.

The success page lists one row per file with a status (`✓ created`, `✓ created as <slug>` for auto-renames, `✗ <error>` for failures) and the new URL.

## CLI — `anchorify upload-folder <dir>`

Recursive: walks the directory tree, flattens nested paths into slugs (`docs/intro.md` → `docs-intro`), publishes each text file via POST `/`.

```bash
anchorify upload-folder ./drafts
anchorify upload-folder ./drafts --project q1-reports
anchorify upload-folder ./drafts --visibility members
anchorify upload-folder ./drafts --dry-run    # list slugs, no POST
```

Flags:

- `--project <slug>` — required when the org has 2+ projects. Without it, the server returns a 400 with the list of projects + the exact retry command.
- `--visibility <tier>` — applies to every file in the batch.
- `--dry-run` — prints `<slug>\t<rel-path>` per file and exits without hitting the server.

Skipped at walk time: dotfiles (`.git`, `.DS_Store`) and `node_modules`. Binaries are rejected per-file ("not yet supported"); use the web upload page for those.

Exit code is `1` if any file errored, `0` otherwise. The first error is printed; the loop continues so a single broken file doesn't sink the batch.

## Moving shares between projects

A share can move to a different project in the same org without re-uploading. Old URLs 301 to the new canonical URL via the [K4 redirect chain](/docs/projects).

- Dashboard: open the share's actions menu (`⋯`) → **Move to…** → pick a project.
- CLI: `anchorify move <slug-or-id> --to <project>`.
- API: `POST /api/v1/shares/<id>/move {project_id|project_slug}`.

Cross-org moves are blocked — use [invites](/docs/invites) to bring the recipient into your org first.
