Skip to content

Content and Media

Theodor Dimitriou edited this page Sep 24, 2026 · 2 revisions

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.

Posts and pages

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.

What a post stores

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.

Drafts, review, scheduling and trash

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.

Revisions and autosave

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

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.

Sections and patterns

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.

Editor side panels

  • 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, tags and taxonomies

  • 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 slugs category, tag, author, date, page, post and search are 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.

Media library

/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.

Accepted files

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 PDF 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.

Image derivatives

Raster images go through the sharp pipeline:

  • WebP derivatives are generated at 400, 800 and 1600 px wide, named <hash>-w400.webp, <hash>-w800.webp and <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 url points at the largest derivative, and thumb_url at the 400 px one. The original is kept at original_url. Set MEDIA_KEEP_ORIGINALS=0 to 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=keep to 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.

Alt text

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

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

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.

SEO fields

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.

Redirects

/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.

Comments and product reviews

Both are off by default. Turn them on under Settings → Reading:

  • "Let readers comment on posts" registers the built-in comment collection.
  • "Let customers review products" registers the built-in review collection. Ratings are whole numbers from 1 to 5. The server marks a review verified_buyer when 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.

Legal pages

/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.

Search

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.

See also

Clone this wiki locally