Working with entries
The collection list, its four statuses, and the three verbs that each write exactly one commit — create, rename and delete.
Every collection you named in src/admin.config.ts gets a list screen at /admin/<collection>/, generated from the same map that generates the sidebar. There is one route behind all of them.
The list
Entries newest first, dated by whichever field your configuration named. The date the list sorts by and the date it displays come from one function, so the order can never disagree with the labels.
Every derived figure on the screen — the tab counts, the sidebar badge, the “waiting on you” total — is computed from the same array the list below renders. A filter is its predicate, so a tab’s count, its contents and its empty state cannot drift apart.
The four statuses
| Chip | Filter tab | What it means |
|---|---|---|
| Published | Published | Live on the site |
| Draft | Drafts | The entry’s draftField is true |
| Ready to publish | Ready | Committed but not yet on the publish branch |
| Needs review | Needs review | Open in a pull request |
The first two come from your content. The last two describe where an edit sits in git, which is something content.config.ts cannot know — Ready fills from the pending-changes queue, and Needs review is unreachable today because a pull-request flow is post-v1. The tab is drawn because the design draws it, and it stays empty rather than being faked from a field that means something else.
Creating an entry
New entry on the list screen. You give it a title, and the address is derived from it underneath the field — lowercased and hyphenated as you type — while staying editable. Correct the address and it stops re-deriving, because an author who corrects an address means it.
Creating writes the file and commits it, then opens the editor on the new entry rather than dropping you back on a list with one more row to find. The route is polled first, because a .md on disk is not yet an entry in Astro’s content store.
The slug rule lives on the server, in the action, where it cannot be skipped. The dialog does not re-check what you typed — a second copy of the pattern in the browser would be a form that rejects something the server would have accepted, which is the version nobody can work around.
Renaming an entry
The rail’s footer shows the entry’s web address with an Edit beside it. A rename moves the file and commits it as one change git reads as a rename (R100), so history follows the entry rather than showing a delete and an add.
Two rules of its own:
- Refused while the rail is dirty. A landed rename navigates to the new URL, which reloads the screen and would take unsaved edits with it. Saving first silently would make one button write two commits.
- The new route is polled before navigating. Astro’s content store re-syncs on its own schedule, and under a production build not at all until the next build. Walking straight into a 404 would read as a failed rename.
A slug already in use answers taken, and names the file that holds it.
Deleting
Deletion is a commit like everything else, so it is two-step rather than modal: the button arms, then confirms. The file is in your history afterwards and the button says so. There is no separate git verb involved — staging a removal is the same git add the save uses.
What a save actually does
Covered in full under The document editor and Details, SEO and History, but the shape is worth having here:
An authenticated editor posts the changed frontmatter keys and, if you touched it, the body. The server patches the frontmatter in place (key order and comments survive, because the round trip goes through YAML’s document mode rather than a re-serialize), replaces the body only if it changed, writes the file and commits it — authored by your account, committed by the deployment’s identity.
The commit subject follows a template you can change: content(blog): update "my-post" out of the box.
Conflicts
When you open an entry, the CMS hashes the file’s bytes. When you save, it hashes them again. If they differ, somebody else changed the file underneath you and the save is refused rather than applied.
The refusal comes back as data — the current bytes travel with it, so the eventual conflict dialog has something to draw. Today it surfaces as one honest sentence in the status line. That dialog is the piece still owed on this screen.
The check is SHA-256 of the file’s own bytes, not the git blob sha, because the blob sha only answers “has the remote moved”, not “did somebody edit this file”.
Troubleshooting
A renamed entry keeps 404ing. The content store has not re-synced. In dev, restart the server; under a production build, the new route exists at the next build. The rename itself already committed.
Save says the entry changed on disk. Somebody else edited it, or you have two tabs open on the same entry. Reload to see the current version — your unsaved work is still banked as a draft, and the History tab has the last twenty saves.
A created entry opens a 404. The store poll timed out. The file and the commit exist; reload.
Two of the four filter tabs are always empty. Expected — see the status table above.