# Editorial operations > Bulk staging, duplication, JSON import with dry-run, full project export, and saved entry views. Day-to-day editorial work is more than one entry at a time. These operations act on many entries — or a whole project — while keeping Content's invariant: **everything stages on a [change set](/concepts/change-sets); nothing publishes implicitly.** ## Bulk staging `POST /v1/projects/:project/entries/bulk` stages a `delete` or `unpublish` across up to 200 entries in one call: ```json { "action": "unpublish", "entryIds": ["ent_01...", "ent_02..."], "changeSetId": "chs_01..." } ``` Omit `changeSetId` to let each entry resolve its change set the normal way (its existing open change set, else an implicit one). The response reports per-entry results — an entry that already belongs to a different open change set fails alone without aborting the batch: ```json { "results": [ { "entryId": "ent_01...", "ok": true, "changeSetId": "chs_01..." }, { "entryId": "ent_02...", "ok": false, "error": "Entry already belongs to another open change set." } ] } ``` `delete` requires the `content:delete` scope; `unpublish` requires `content:write`. CLI: `myna entries bulk --action unpublish --ids ent_01...,ent_02...` (non-zero exit if any row fails). The dashboard exposes the same operation through checkbox selection on the entry table. ## Duplicate an entry `POST /v1/projects/:project/entries/:entry/duplicate` creates a new draft from the source's current draft data — "create from existing". The copy derives a fresh unique slug, records `Duplicated from ` as its change summary, and stages a `create` item like any other new entry. Optional body: `{ "changeSetId": "chs_..." }`. CLI: `myna entries duplicate posts/launch`. Dashboard: the ⋯ menu in the entry editor. ## Import with dry-run `POST /v1/projects/:project/entries/import` writes entries in bulk: ```json { "collection": "posts", "dryRun": true, "mode": "upsert", "changeSetId": "chs_01...", "entries": [ { "slug": "hello", "data": { "title": "Hello", "body": "..." } }, { "data": { "title": "No slug — derived automatically" } } ] } ``` Imports are safe to run repeatedly: - **Dry run.** With `"dryRun": true`, every row is validated against the collection's deployed schema (including [locale rules](/schema/defining-schemas)) and the response reports the outcome of each row. Nothing is written. Matching is identical to a real run, so the counts are exact. - **All-or-nothing.** Without `dryRun`, rows are validated and checked for conflicts before anything is written. If any row fails, none are applied — including a row whose entry is already staged on another open change set. - **Matched by slug.** `mode: "upsert"` (the default) updates a row whose slug already exists. A row whose data is unchanged is reported `unchanged` and writes no revision. Use `mode: "create"` to reject an existing slug as an error instead. - **Never publishes.** Writes stage on a change set: the one named in `changeSetId`, or a new one titled `Import into `. If every row is unchanged, no change set is created and `changeSetId` is `null`. The response carries per-row outcomes and a summary: ```json { "valid": true, "changeSetId": "chs_01...", "summary": { "created": 3, "updated": 1, "unchanged": 21, "invalid": 0, "skipped": 0 }, "results": [{ "index": 0, "ok": true, "outcome": "unchanged", "slug": "hello" }] } ``` Limits: 500 rows per request. ### From the CLI The CLI reads JSON, or a directory of Markdown files with frontmatter: ```bash $ myna entries import ./content/posts --collection posts --dry-run $ myna entries import ./content/posts --collection posts $ myna entries import ./posts.json --change-set chs_01... ``` Frontmatter keys become fields, the filename becomes the slug unless frontmatter sets one, and the document body is assigned to `--body-field` (default `body`). Frontmatter supports quoted and bare scalars, numbers, booleans, null, and inline `[a, b]` arrays. Anything else is rejected with the file and line. ## Syncing a content directory `myna sync` reconciles a directory of content files against collections in one pass: ```bash $ myna sync ./content --dry-run $ myna sync ./content $ myna sync ./content --delete-missing --confirm-delete ``` Each immediate subdirectory becomes a collection of the same name (`./content/posts` → `posts`); pass `--collection` to treat the whole directory as one collection. All changes stage on a single change set. `--delete-missing` additionally stages deletion of entries whose slug has no matching file. It requires `--confirm-delete`, and lists every entry it will remove. Run `--dry-run` first. An entry that was never part of your content directory is indistinguishable, to `sync`, from one you intended to delete. ## Editorial ordering `POST /v1/projects/:project/collections/:collection/reorder` sets a collection's arrangement. ```bash $ myna entries reorder projects --slugs myna,erlcx,handitoff,fleck $ myna entries reorder projects --move fleck --after myna ``` Positions are stored as **fractional ranks**: strings compared lexicographically, with room always available between any two (`a0 < a1 < a1V < a2`). Moving one entry writes exactly one row, regardless of collection size. > **Ordering is not staged on a change set.** It carries no revision and is visible to readers as soon as it is applied — which is why the request must include `"confirm": true`. > > Use it to arrange content, not to change it. Anything that needs review belongs in the entry's fields. Collections with no explicit arrangement are ordered newest first. Ranked entries sort before unranked ones, so a new entry in an arranged collection appears at the top. ## Full project export `GET /v1/projects/:project/export` (scopes `content:read` + `schema:read`) returns one JSON document for portability and backups: | Key | Contents | |---|---| | `project` | Project settings, locales, publish gates, origins | | `collections` | Each collection's key, metadata, and deployed canonical schema | | `entries` | Every live entry with both `draft` and `published` data | | `assets` | Asset metadata: filename, content type, checksum, alt, caption, tags | Asset binaries stay in storage — the metadata includes the `checksum` so a migration can verify copies. CLI: `myna projects export > backup.json`. Export pairs with import for environment cloning: export from production, import into staging (see [environments and promotion](/schema/deploying)). ## Saved views Saved views are named entry-table presets — collection, status filter, and search — stored per project so humans and agents see the same lists: - `GET /v1/projects/:project/views` — list (`content:read`) - `POST /v1/projects/:project/views` — create, e.g. `{ "name": "Drafts needing review", "filters": { "collection": "posts", "status": "changed", "q": "" } }` (`content:write`) - `DELETE /v1/projects/:project/views/:view` — remove (`content:write`) `filters` is a free-form object interpreted by the client; the dashboard reads `collection`, `status`, and `q`, and applies a view from the Views menu on the content screen. SDK: `client.projects.views(...)`, `createView(...)`, `deleteView(...)`.