stnd.buildSTANDARD MANUAL2026-09-20

Building a Module

Recipes for the day-to-day — routes, styles, config, dependencies, disabling a module — plus the full manifest key reference.

Building a Module

Add a route

routes: [
  { path: "/blog", entrypoint: "./routes/index.astro" },
  { path: "/blog/[slug]", entrypoint: "./routes/post.astro" },
],

Add global styles or scripts

styles: ["./styles/blog.scss"],  // injected on every page
scripts: ["./client.js"],         // JS files or CDN URLs

Contribute to the site’s nav, title, or any other config

Modules deep-merge into the global config via the config key:

// modules/seo/index.module.js
export default {
  id: "seo",
  config: {
    nav: {
      footer: [{ title: "Privacy", url: "/privacy" }],
    },
  },
};

Arrays (like nav.header) are concatenated across modules — each
module appends its items. Objects are recursed. Scalars are
protected: two modules claiming the same scalar key is a build error.

Config Conflict: Key "title" was claimed by both "core" and "seo".
Only one module may define a scalar config value.
Use arrays or nested objects for multi-module contributions.

Your top-level astro.config.mjs options always win last, silently —
they’re the final override. The merged result is available everywhere via
virtual:stnd/config:

import config from "virtual:stnd/config";
console.log(config.nav.header); // all modules' nav items, combined

Declare a dependency

export default {
  id: "shop",
  dependencies: ["stnd-styles", "core", "@stnd/fonts/kalice"],
  // ...
};

The loader guarantees a dependency is ready before your module runs — a
topological sort (Kahn’s algorithm) does the ordering, so you never have
to hand-tune load order yourself. Unresolved dependencies log a warning
but don’t crash; circular dependencies throw immediately.

Provide a value to the runtime store

For state other modules or components need without importing you
directly (a simple Map-based provide/inject):

// in the module manifest
store: {
  user:   "./models/Visitor.js#visitorStore",  // named export
  search: "./search.js",                        // default export
}
// in any component
import { inject } from "@stnd/store";
const user = inject("user");

Values are initialized before any component mounts.

Disable a module without deleting it

export default {
  id: "analytics",
  status: "disabled", // loaded but not processed
  // ...
};

Or exclude a gold standard module entirely from your Astro config:

standard({
  moduleExclude: ["@stnd/launcher"],
});

If you need to know the load order

  1. Gold standard modules — internal system modules, in their declared order
  2. moduleLoad entries — your explicit additions, in the order you wrote them
  3. Discovered modules — auto-found in modules/, alphabetical
  4. Dependencies — pulled in before the module that requires them

Explicit modules (moduleLoad) always load before auto-discovered ones.
Within a tier, alphabetical order is the stable tiebreaker.

Quick reference — every manifest key

Key Type Description
id string Required. Unique identifier. Duplicate IDs throw.
name string Display name for logs and dev tools.
description string Short description.
status "enabled" | "disabled" Skip processing when "disabled".
dependencies string[] Module IDs that must load first.
config object Merged into global config. Scalar conflicts throw.
routes { path, entrypoint }[] Astro routes to inject.
styles string[] CSS/SCSS files, injected on every page.
scripts string[] JS files or CDN URLs, injected on every page.
middleware string[] | { entrypoint, order }[] Request handlers. order controls sequence.
actions string Path to Astro Actions file.
hooks { [hookName]: entry[] } UI zones and lifecycle hooks — see Hooks & Extension Points.
store { [key]: "path#export" } Runtime provide/inject registrations.
aliases { [alias]: path } Vite path aliases.
content string Path to content collections definition.