Repository navigation
Content and Media
How writing and publishing work in AstroBaaS. This page covers posts and pages, the editor, categories and taxonomies, the media library, SEO fields, redirects, comments, legal pages and search. It is for editors and operators, and for developers who want to know what the content model stores.
A page is a post with kind: "page". Both use the same record, the same editor and the same revision history. Only the address differs.
| Kind | Address | In the blog, RSS feed and default GET /api/posts? |
|---|---|---|
| Post (article) | /blog/<slug> |
Yes |
| Page | /<slug> |
No |
Records with no kind count as articles, so content written before pages existed keeps its old behaviour. In the editor, choose the type under Publish settings → Type.
A page cannot use a slug that a built-in route already serves (for example blog or login). The reserved list is in src/lib/reserved-slugs.ts. To make a page the site's home page, pick it under Settings → General → Home page.
Over the API, GET /api/posts returns articles only. Add ?kind=page for pages only, or ?kind=all for both. See Headless and REST API.
| Field | Notes |
|---|---|
title, slug, content, excerpt
|
content is sanitised HTML |
status |
draft, review, scheduled, published, trashed
|
kind |
post (or absent) or page
|
publish_date |
Used for scheduling and display |
category_id |
One category per post |
tags |
A list of free-text tags |
terms |
Custom taxonomy assignments, such as { "brand": ["ray-ban"] }
|
featured_image |
Image URL |
meta_title, meta_description, focus_keyphrase, noindex
|
SEO, see below |
pinned, menu_order
|
Listing order, see below |
locale, translation_of
|
See Internationalisation |
author_id, views
|
Set by the server |
The full type is in src/core/models.ts.
| Status | Meaning |
|---|---|
draft |
Not public |
review |
Waiting for an editor to publish it |
scheduled |
Goes live when publish_date passes |
published |
Public |
trashed |
In the bin. Filter the Posts screen by "Trashed" to see these |
Review. Turn on Settings → Reading → "Hold posts for review before they go live". Authors and managers then cannot publish. They set a post to review and an admin or editor publishes it. An author who tries to publish is refused with a message saying why, and the post is not silently saved as a draft. With the setting off, anyone who can write a post can publish it.
Scheduling. Set the status to Scheduled and pick a publish date. A background scheduler checks every 60 seconds and publishes posts whose date has passed. It also runs once at start-up.
| Variable | Effect |
|---|---|
SCHEDULER_INTERVAL_MS |
Check interval in ms (default 60000, minimum 100) |
SCHEDULER_DISABLED=1 |
Turns the scheduler off |
SCHEDULER_LEASE=0 |
With several processes, lets every process run the sweep instead of one elected leader |
If a scheduled post misses its time, it shows up in the dashboard attention feed and under Background jobs. Source: src/lib/scheduler.ts.
Bulk actions. On the Posts screen, select posts and choose Publish, Draft, Move to Trash or Delete. Delete cannot be undone.
Ownership. Editors and admins can edit and delete anyone's posts. Authors and managers can only edit their own.
A revision is a snapshot of a post's title, content and excerpt, taken just before a save overwrites them. The edit screen autosaves your work in progress as a revision, but autosave never publishes. The History panel lists up to 25 recent revisions, and restoring one is an ordinary save, so a restore can itself be undone.
A new revision is skipped when nothing changed since the last one.
| Variable | Default | Effect |
|---|---|---|
REVISIONS_KEEP |
20 |
Revisions kept per post (maximum 500) |
REVISIONS_DISABLED=1 |
off | Turns off revisions and the autosave endpoint |
Source: src/lib/revisions.ts.
The editor is a rich-text editor that stores sanitised HTML. The toolbar has bold, italic, underline, headings 1 to 3, lists, links, images, alignment and Embed a video or map. Embeds support YouTube, Vimeo and OpenStreetMap links.
Preview opens the post in a separate tab, rendered by the server exactly as it will appear.
AstroBaaS does not use a block tree. Content stays a plain HTML string, so the API, the RSS feed, search, revisions and a headless front end all read the same thing.
- A section is a layout element, such as a hero, columns or a call to action. It is stored as an allow-listed CSS class (prefixed
ab-) on ordinary HTML. The Sections panel inserts one at the cursor. Once a section is selected you can move it up or down, duplicate it or delete it. Themes restyle sections, and plugins can add them. - A pattern is a ready-made arrangement of sections, such as "hero, three feature columns, call to action". Inserting a pattern pastes its HTML and nothing links back to it, so a later theme update cannot rewrite your pages.
A pattern is only offered if its markup passes the HTML sanitizer unchanged. Tools → Patterns lists the patterns that were rejected. Tools → Section health reports section classes in your content that this build no longer recognises. Those still render, but unstyled.
Settings → Reading → "New posts start from" and "New pages start from" choose the pattern a blank post or page starts with.
Sources: src/core/sections.ts, src/core/patterns.ts. Theme authors should see Themes.
- Content analysis (edit screen): SEO and readability checks, computed in your browser as you type. They use the focus keyphrase, meta title and meta description. Nothing is sent to the server.
- AI panel: writing help and draft translation for a chosen field. It needs an AI provider configured under Settings → Advanced → AI assistant.
- Taxonomy terms (edit screen): appears when a custom taxonomy applies to posts or pages.
-
Categories (
/admin/categories) have a name, a slug and an optional description. A post has at most one category. Admins, editors, authors and managers can all open this screen. - Tags are free-text labels on the post.
-
Custom taxonomies are defined on the Content types screen, and each is separate from categories and tags. A taxonomy has a slug, a label, and an Applies to list (
post,page,product, or any content type name). There is also a Public page switch, which gives each term an address at/t/<taxonomy>/<term>. Terms are identified by slug, so renaming a term does not unassign it. You can define up to 30 taxonomies. The slugscategory,tag,author,date,page,postandsearchare reserved. Removing a taxonomy leaves the assignments in place. They stop being read, and come back if you define the taxonomy again. Source:src/core/taxonomy.ts.
/admin/media. Upload by dragging files in or by browsing. The library has grid and list views, search, folder filters and infinite scroll. Anyone with author_posts can upload (admin, editor, author, manager). Replacing or deleting someone else's file needs manage_all_posts.
A file's type comes from its content (magic bytes), never from its name or declared type.
| Kind | Formats | Size limit |
|---|---|---|
| Images | PNG, JPEG, GIF, WebP | 10 MB |
| Vector | SVG (sanitised; the cleaned copy is what gets stored) | 10 MB |
| Documents | 10 MB | |
| Video | MP4, WebM | 100 MB, set with MEDIA_MAX_VIDEO_MB (maximum 2048) |
| Text | Plain text, Markdown (always served as text/plain) |
10 MB |
The upload box names only the image and video formats, but the server also accepts the others listed here. Source: src/lib/media/ingest.ts.
Files are stored under /uploads/<yyyy>/<mm>/<hash>.<ext>. The name is a hash of the content, so uploading the same bytes twice reuses one file. If MEDIA_SCAN is configured, each upload is virus-scanned before anything else happens.
Raster images go through the sharp pipeline:
- WebP derivatives are generated at 400, 800 and 1600 px wide, named
<hash>-w400.webp,<hash>-w800.webpand<hash>-w1600.webp. - Images are never enlarged. Only widths smaller than the original are generated, plus one derivative at the original's own width if that is under 1600. An image narrower than 400 px gets a single derivative at its own width.
- The record's
urlpoints at the largest derivative, andthumb_urlat the 400 px one. The original is kept atoriginal_url. SetMEDIA_KEEP_ORIGINALS=0to delete originals after the derivatives are made. - Camera metadata (EXIF, such as GPS location and device serial) is removed from the stored original without re-encoding it. Set
MEDIA_ORIGINAL_EXIF=keepto keep it. - Width and height are recorded with EXIF orientation applied, so portrait phone photos report portrait dimensions.
If sharp is missing or a derivative fails, the upload still succeeds. The problem is recorded on the media record and reported by the deep health check. SVGs are not rasterised. The file is served at every size, with dimensions read from the markup.
Click Describe on a file to set its alt text: a description for someone who cannot see the image. Leave it empty for decorative images. Alt text is limited to 300 characters, and angle brackets and quotes are removed.
Folders are labels on the record, not directories on disk, so a file's URL never changes when you move or rename a folder. The library shows a row of folder chips once any file has a folder, and filters by them. To set a folder, send a PATCH to /api/media/update with { "ids": [...], "patch": { "folder": "Products" } }.
Replace swaps the file behind a media record and keeps its id, alt text and upload date. The new file gets a new address, because uploads are cached as immutable. AstroBaaS then rewrites every stored reference to the old URLs. That covers post and page content, featured images, product images (including variants and translated descriptions) and brand logos. Old files are removed unless another record still uses them. URLs hard-coded in a theme or in an external site are not rewritten, and those will break. Source: src/lib/media/replace.ts.
For where uploads live on disk and how they are backed up, see Backups and storage.
Each post and page has:
| Field | Notes |
|---|---|
| Meta title | Counter guides you toward 60 characters |
| Meta description | Counter guides you toward 160 characters |
| Focus keyphrase | Used by the content-analysis panel |
Hide from search engines (noindex) |
Also leaves the post out of the sitemap and the feed |
Pin to the top (pinned) |
Listed above the newest posts in every listing |
Manual order (menu_order) |
Lower numbers come first. Posts without a number fall back to date order, after the numbered ones |
The site also generates a sitemap (/sitemap.xml), an RSS feed (/rss.xml), robots.txt (with extra rules and a crawler policy under Settings → Reading), Open Graph images (/og/<slug>.png) and hreflang tags on multilingual sites. Settings → Reading also has the table-of-contents threshold and the related-posts count.
/admin/redirects (admin, editor, manager) puts two things on one screen:
- Rules: match a path and send it to a target with 301 (moved permanently), 302 (temporary) or 410 (gone). Each rule can carry a note.
- Addresses that led nowhere: a report of 404s, sortable by paid clicks lost, times requested or most recent, so you can turn a dead URL into a rule straight away.
The WordPress importer writes 301 rules for old permalinks automatically. See Importing.
Both are off by default. Turn them on under Settings → Reading:
- "Let readers comment on posts" registers the built-in
commentcollection. - "Let customers review products" registers the built-in
reviewcollection. Ratings are whole numbers from 1 to 5. The server marks a reviewverified_buyerwhen the reviewer's email placed an order for that product, and reviewers cannot set that flag themselves.
Both are ordinary custom content types. They are publicly readable, publicly writable and moderated: every new comment or review waits as pending until someone approves it on its entries screen under Collections. Both use the same honeypot, rate limit and optional proof-of-work check as other public forms. Threads are one level deep. Readers cannot edit or react to comments, and nobody can reply to reviews. Source: src/core/builtin-collections.ts.
/admin/legal (admin only) is a library of starting texts in English, Greek and German:
| Template | Notes |
|---|---|
| Privacy policy | |
| Terms of service | |
| Cookie policy | |
| Imprint / legal notice | |
| Returns (shops) | Only offered when commerce is on. It links to the EU withdrawal notice at /legal/withdrawal
|
Activate creates a normal page as a draft, filled in with your site title, URL and trader details from Settings. A missing value appears as a visible [fill in: …] marker, never as a blank. Read the draft, adapt it, then publish it. These are templates, not legal advice. Source: src/lib/legal/templates.ts.
GET /api/search?q=<words> searches published articles (pages and noindex posts are excluded). Queries under 2 characters return nothing. Results are ranked by relevance, with newer posts first among equal matches. On a multilingual site they can be narrowed to one locale. The blog listing accepts ?q= too.
Settings → Reading → Search synonyms adds a synonym table, used by both blog search and product search. Plugins can hook into search. See Headless and REST API for the response shape.
AstroBaaS is free software under GPL-3.0-or-later. This wiki describes the latest release; the repository and its CHANGELOG are the final word. Found a mistake here? Open an issue.
Getting started
Using the CMS
Building with it
Extending
Commerce and email
Operating
Project