Repository navigation
Importing
How to bring existing content into AstroBaaS, and how to get your own content out again. This page covers the WordPress importer (in the admin and on the command line), the WooCommerce importer, the Markdown importer, and CSV/JSON export and import of collections. It is for operators moving a site onto AstroBaaS.
| Source | Tool | Where |
|---|---|---|
| WordPress posts and pages (WXR export) |
npm run import:wp, or Admin → Import a site |
CLI or admin |
| WooCommerce shop (products, orders, customers, blog) | npm run import:woo |
CLI only |
| A folder of Markdown files | npm run import:md |
CLI only, JSON database only |
| Your own posts, pages and collections (CSV or JSON) | Admin → Import a site → Your own content | Admin or API |
All four share one rule: an import cannot be undone. Take a backup first (see Backups and storage), and rehearse before you commit. The WordPress, WooCommerce and CSV/JSON importers all rehearse by default and write nothing until you confirm.
In WordPress, go to Tools → Export → All content and download the .xml file.
Admin → Import a site (/admin/import, admin only). Upload the export and choose options:
| Option | Default | Meaning |
|---|---|---|
| Pages as well as posts | on | Import WordPress pages as AstroBaaS pages |
| Download images and files from the old site | off | Fetch the media library. The old site must still be online. At most 100 files per run from the admin |
| Include content WordPress had in the bin | off | Also import trashed items |
| Custom post types | empty | Comma-separated type names (such as portfolio, recipe) to import as posts |
See what would happen runs a dry run and shows a summary: counts, the authors found, what will not be imported and why, and any failures. The button that actually writes appears only after you have seen that summary.
Admin limits:
| Limit | Value |
|---|---|
| Export file size | 24 MB (larger files must use the CLI) |
| Media files per run | 100. The rest are reported as skipped, with the cap named |
| Import requests | 10 per hour |
The endpoint behind this screen is POST /api/import/wordpress (multipart). It needs the import_content capability, which only admins have. It does a dry run unless you send dry_run=false. Source: src/pages/api/import/wordpress.ts.
npm run import:wp -- ./export.xml # rehearse, write nothing
npm run import:wp -- ./export.xml --apply # perform it
npm run import:wp -- ./export.xml --apply --media # ...and fetch the files| Flag | Meaning |
|---|---|
--apply |
Perform the import. Without it, nothing is written |
--media |
Fetch the media library from the old site |
--trash |
Include content WordPress had in the bin |
--no-pages |
Import posts only |
--types a,b |
Also import these custom post types, as posts |
--author <email> |
Put imported content under this account. The default is the oldest administrator |
Use the CLI for big sites. It has no upload limit or HTTP timeout, accepts exports up to 200 MB, and fetches up to 2000 media files per run. The CLI and the admin use the same code in src/lib/import/, so they make the same decisions. Both write through the normal storage layer, so they work on every database driver. Source: scripts/import-wp.mjs.
| WordPress | AstroBaaS |
|---|---|
| Post | Post (/blog/<slug>) |
| Page | Page (/<slug>) |
| Listed custom post type | Post |
| Title, content, excerpt, slug | Same. Content is HTML-sanitised on the way in |
| Publish date (GMT preferred) | Publish date |
| First category | The post's category, created if missing. The display name is kept |
| Tags | Tags |
| Attachments (with media on) | Media library items, through the same upload checks as a normal upload |
| Old permalink that moved | A 301 redirect to the new address |
WordPress statuses are converted like this:
| WordPress | AstroBaaS |
|---|---|
publish |
published |
pending |
review |
future |
scheduled (keeps its date, and the scheduler publishes it) |
private |
draft, so private content is not published by the migration |
| anything else | draft |
Redirects are written after the content, and only when the URL actually changed. WordPress's default /YYYY/MM/slug/ becomes /blog/slug. A page at /slug/ usually stays put and gets no rule. If a slug collided with existing content and was renamed (for example -2), the redirect points at the renamed address. A path that already has a redirect is left alone. New rules take effect immediately, without a restart.
-
User accounts. Authors found in the export are listed, but no accounts are created. Invite those people yourself, then reassign their posts. All imported content belongs to one account (see
--author). - Comments, custom fields (postmeta) and menus.
- Products, orders and customers. WooCommerce keeps these outside the WXR export, so use the WooCommerce importer below.
- Featured images. The importer reads which attachment was the featured image, but does not attach it to the imported post.
- Image URLs inside post content are not rewritten to the imported media copies. They still point at the old site.
- Trashed items (unless you asked for them), auto-drafts WordPress never used, and custom post types you did not list. Each is reported as skipped, with a reason.
- Media URLs pointing at private or loopback hosts, and files larger than the media library's 10 MB limit. These are refused and reported.
Re-running an import is safe. Each imported record is stamped with its WordPress id (wp_id). Anything already imported, including media, is skipped with "already imported by an earlier run". If a media fetch stopped half-way, run the same command again.
A WXR file does not contain products, orders or customers. The WooCommerce importer reads normalised JSON instead: one directory containing any of these files:
products.json categories.json brands.json
customers.json orders.json blog.json
A missing file is fine and is skipped with a note. A corrupt one stops the import. The repository does not include a tool that produces these files from a WooCommerce database. The expected shape is shown by the synthetic sample data in data/import.example/ and the input types in src/lib/import/woo.ts.
npm run import:woo -- data/import.example # rehearse with sample data
npm run import:woo -- /path/to/my-export --apply # perform it
npm run import:woo -- /path/to/my-export --apply --media ./wp-content/uploads| Flag | Meaning |
|---|---|
--apply |
Perform the import. Without it, nothing is written |
--media <dir> |
After importing, add product images from this folder (your extracted wp-content/uploads) to the media library and point products at the stored copies |
--no-posts |
Shop only. Skip blog.json
|
--author <email> |
Put imported posts under this account. The default is the oldest administrator |
--site-stopped |
Required with --apply on the doc-blob driver (DATABASE_URL set without DATABASE_DRIVER=relational). It confirms the site is stopped, because on that driver the site and the import would overwrite each other's writes |
Keep real exports out of version control. They contain customer names, emails, phone numbers and addresses. data/import/ is gitignored for this reason.
What it does:
-
Prices must be whole numbers of cents (or strings of digits). A float, a comma decimal,
nullor a negative value is refused and reported, never rounded. - Categories keep their parent. Brands keep their spelling in any script and get a transliterated slug.
- Customers get their email, name, phone and address. Passwords are not imported.
-
Orders keep their number, currency, total, line items and contact details, and are linked to the imported customer. WooCommerce statuses map to AstroBaaS statuses of the same name: completed, processing, on-hold, pending, cancelled, refunded, failed. Any unrecognised status becomes
on-hold, so no automatic process touches it until a person looks. -
Blog posts from
blog.jsoncome in as published or draft posts. - The shop is switched on if it was off, when products or orders are imported. It is never switched off.
- References are resolved after their targets exist, so an order never points at a customer that does not exist.
- Re-running is safe. Records are recognised by their
wp_id, and brands also by name.
Source: scripts/import-woocommerce.mjs. See Commerce.
npm run import:md -- ./content/postsEach .md or .markdown file in the folder becomes a post. YAML frontmatter is read for these keys:
| Key | Use |
|---|---|
title |
Title. Without it, the filename (dashes and underscores become spaces) |
slug |
Slug. Without it, a slug made from the title |
status |
draft, review, scheduled, published or trashed. Any other value becomes draft
|
excerpt |
Excerpt |
tags |
An inline list: [a, b, c]
|
category |
Category name. Created if missing |
date |
Sets the created and updated dates |
featured_image |
Stored as the featured image URL |
Other keys are ignored. The frontmatter parser handles plain values (text, numbers, true/false) and inline lists only.
The Markdown converter is deliberately minimal. It handles paragraphs, headings, code blocks, lists, links, bold, italic and inline code. For anything richer, put HTML in the file.
Limitations:
- It works on the JSON file database only. It writes the
db.jsonfile directly (honouringDB_PATH), and refuses to run when the site uses libSQL or a relational database. The server keeps its own copy of the database in memory, so stop the site while this runs. - There is no dry run. Files whose slug already exists are skipped with a warning.
- Posts go to the first user in the database. They are always articles, never pages.
- The content bypasses the API, so it is not HTML-sanitised or added to the change feed, and no webhooks fire.
Source: scripts/import-md.mjs.
The lower half of Admin → Import a site, Your own content, moves posts, pages and any content type out as a file and back in.
Choose a collection and a format, then click Export:
| Format | Notes |
|---|---|
| CSV | Opens in Excel. Lists and repeaters are JSON in a single cell, booleans are true/false, dates are ISO-8601. Cells that would start a spreadsheet formula are escaped |
| JSON | The same rows as JSON |
Post and page columns: id, title, slug, status, excerpt, content, category_id, tags, featured_image, locale, translation_of, meta_title, meta_description, focus_keyphrase, noindex, pinned, menu_order, publish_date. View counts and created_at are left out on purpose. Content types export id plus their declared fields, and _status if they are moderated.
Directly: GET /api/transfer/<collection>?format=csv|json, where <collection> is post, page or a content type name. Any role that can write posts can export (admin, editor, author, manager). You only get what your role can see, so an author does not receive colleagues' drafts.
Choose a file and click Check the file. The preview lists how many rows will be created, updated or skipped, with a reason for each skip. Import for real then applies it.
- Rows are matched by
idfirst, then byslug, never by position or title. A sorted spreadsheet imports correctly. - An
idthat matches no record here is skipped. It is not created, so a file from another site cannot quietly duplicate everything. - A row that matches nothing and has a slug is created. A new post or page needs a slug.
- Updates are partial: only the columns in the file change.
- Every row is validated against the collection's rules, and invalid rows are skipped.
- The limit is 10,000 rows per file.
Directly: POST /api/transfer/<collection> with { "body": "<file text>", "format": "csv" | "json", "apply": true }. Without "apply": true, the response is the preview. Import needs import_content (admin only). Source: src/lib/content-transfer.ts.
- Whole-site backup: Admin → Tools → Download backup (the database plus uploads, as one JSON file). See Backups and storage.
- One person's data: Admin → Data requests exports everything held about an email address. See Security.
- The API: every collection is readable over REST. See Headless and REST API.
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