Configuration
One config
Section titled “One config”The whole shape of the project is one typed file. astroid generate turns it
into src/schema.ts, src/worker.ts, and src/middleware.ts.
import { defineAstroid } from "astroidjs";
export default defineAstroid({ key: "example", archetype: "storefront", theme: { name: "Example Organization", colors: { brand: "#5b4bff" } }, sections: ["hero", "banner", "productGrid", "locationHours", "contact"], commerce: { provider: "square" }, deploy: { platform: "cloudflare" },});One brand per project. Every site Astroid targets serves a single brand from a single deploy, so the config describes one brand, not an array. What multiplexes is editors (Louise’s org plugin) and audiences (a gated portal beside the public site).
Archetypes
Section titled “Archetypes”marketing (the lean brochure floor), storefront (DTC shop), wholesale
(B2B/private-label), portfolio (gallery + client portal). An archetype is a
preset of defaults—which sections are on, which tables exist—that the site
then tunes, not a fork.
Sections
Section titled “Sections”The editable home page is an ordered list of section types. Astroid ships 15:
hero, featureGrid, cta, gallery, media, splitImage, steps, banner,
faq, pricingTiers, testimonial, aboutIntro, productGrid,
locationHours, contact.
The vocabulary is derived from the catalog, so a section name that has no component is a compile error rather than a page that silently fails to render.
Inquiries
Section titled “Inquiries”A site with a contact section or the wholesaleInquiry module captures
inquiries. It gets an inquiries table, an Inquiries tab in the editor, an
inbox count on the Home dashboard, and a public contact form route,
POST /api/louise/forms/inquiries. That route stores a submission, emails the
owner the full message, and sends the sender a short confirmation that quotes
nothing they wrote.
inquiries overrides the detection:
inquiries: true, // on, for a bespoke section Astroid can't seeinquiries: false, // off, even with a contact sectioninquiries: { publicForm: false }, // on, without the public form routeUse { publicForm: false } when the site has its own contact endpoint that
writes the inquiries table. The table, the Inquiries tab, and the inbox count
stay, and the generated worker doesn’t mount the public form route. Without
it, the generated route stays reachable beside your endpoint, with none of
your endpoint’s captcha, field limits, or mail rules. The scaffolded
src/pages/contact.astro posts to the generated route, so point its form at
your endpoint or remove the page. publicForm defaults to true, so
inquiries: {} is the same as inquiries: true.
An app with no editor
Section titled “An app with no editor”Every project is a Louise-edited site unless it says otherwise. An app with no
pages to edit, such as an order-ahead app whose menu comes from Square, sets
editor: false:
export default defineAstroid({ key: "order", archetype: "storefront", editor: false, theme: { name: "Example Organization", colors: { brand: "#5b4bff" } }, commerce: { provider: "square", pipeline: false }, modules: ["pwa"], portal: { enabled: true }, deploy: { platform: "cloudflare", migrations: false },});Choose it when nothing in the project is edited in place, and its few settings belong to a site that has an editor. That site stays the only place they’re edited: the app reads them from the site’s database, and never writes them.
What the shape drops:
- The editor. The generated worker and middleware carry no editor routes,
no
./auth.jsseam, and no edit mode. The worker keeps the gate, with a resolver that never finds an editor, so it refuses everything under/api/louiseexcept the public status route. - The content tables.
src/schema.tsemits nopagesor versions, and no framework tables. To read one another app owns, such assiteSettings, import it fromlouise-toolkit/dbwhere you query it, so drizzle-kit never writes a migration for a table this app doesn’t own. - The editor’s bindings.
wrangler.jsoncbinds no draft buffer, media bucket, Images, Workers AI, or vitals dataset, and no mail unless a portal sends password resets.astroid doctordoesn’t ask for them. - The editor’s crons. There’s no daily health scan, so an app with nothing
scheduled has no
triggersand noscheduledhandler.
What stays: the rate limiter, the CSP, the security headers, the public status
route, and the portal, pwa, commerce, map, and tenancy modules.
defineAstroid refuses every option that configures the editor alongside
editor: false (sections, sectionCatalog, blockCatalog, media,
pages, settings, inquiries: true or an inquiries object,
deploy.mediaBase, and the realtime and wholesaleInquiry modules), rather
than accept a setting nothing reads.
The app is API-first. Its web client is the first client of a versioned JSON
API under /api/v1, which a native client can later call as well. The
middleware rate-limits every POST under that prefix per client IP, and the
editor’s sign-in rules are gone. A route there answers JSON, reads its input
from the body or the URL, and needs no browser-only header.
To take payments without the webhook receiver, queue, and catalog cron, which
the site with the editor runs against the same account, add pipeline: false
to commerce (see Commerce). To share that site’s
database, bind it by id and set deploy.migrations: false, so only the site
migrates it.
create-astroid --app scaffolds the shape: the same template without the
editor’s files, plus an app layout, a home screen, and the root of the
/api/v1 API.
Business facts
Section titled “Business facts”A site’s time zone, currency, country, and locale are facts about the business,
and louise-toolkit takes each as a parameter rather than guessing it. State them
once in business:
business: { timeZone: "Europe/Berlin", // IANA currency: "EUR", // ISO 4217 country: "DE", // ISO 3166-1 alpha-2 locale: "de-DE", // BCP 47},None has a default, since a default would be a guess about someone else’s
business, and a wrong one fails quietly: opening hours an hour off, or a charge
in the wrong currency. commerce requires currency, because every charge and
catalog price uses it. defineAstroid validates each fact with Intl, and
refuses, for example, a lowercase "eur" or an Open Graph "de_DE".
Read them through astroidBusiness, and pass them to the toolkit. Name a fact
to get it as a string, or an error naming the field when the config doesn’t
state it:
import { astroidBusiness } from "astroidjs";import { formatMoney } from "louise-toolkit/commerce";import astroidConfig from "../astroid.config";
const price = formatMoney( { amount: 1250, currency: astroidBusiness(astroidConfig, "currency") }, { locale: astroidBusiness(astroidConfig, "locale") },);locale also sets the scaffold’s <html lang>, and it’s the default for
seo.locale, in Open Graph form: de-DE gives og:locale de_DE. Set
seo.locale only when the two differ.
create-astroid takes them as --time-zone, --currency, --country, and
--locale, and prompts for each in a terminal. --commerce needs
--currency.
Agency credit
Section titled “Agency credit”An agency that builds the site can credit itself in the footer. It’s a site
fact, so there’s no default: without credit, nothing renders.
credit: { name: "Example Organization", href: "https://example.com", logo: "/credit-mark.svg", // optional rel: "noopener", // optional; add nofollow if you want it},The scaffold’s layout renders <Credit config={astroidConfig} /> in a footer
when credit is set. A site with its own footer imports it from
astroidjs/components/Credit.astro and puts it there. It renders “Site by”
and the name, small and at 70% of the theme’s text color, and label changes
the words before the name.
The mark is drawn as a mask filled with the text color, so one single-color SVG
matches light, dark, and every theme; its own colors are ignored. It’s hidden
from screen readers, which read the name once. defineAstroid requires href
to be an absolute http or https URL, and logo to be a root-relative path
or an https URL.