How First-Party Block Modules Are Structured in Instatic: The Complete Guide

Instatic organizes first-party block modules under src/modules/base/, where each block self-registers via side-effect imports in index.ts and exposes a TypeBox schema, React editor component, and optional CSS modules.

Instatic is a visual site builder that relies on a modular architecture for its editing interface. The first-party block modules—built-in elements like buttons, images, and layout containers—are centrally organized under src/modules/base/ to ensure consistent registration patterns and lazy loading by the admin bundle. Understanding this structure is essential for extending core functionality or debugging editor behavior.

Registration Hub at src/modules/base/index.ts

The src/modules/base/index.ts file serves as the central registration hub that src/admin/AdminEntry.tsx imports lazily. This file triggers side-effect registrations for every built-in block by importing each module folder directly:

// src/modules/base/index.ts
import './body'            // Page body – required for every new page
import './container'       // Layout containers
import './loop'            // Repeating sections
import './text'            // Typography
import './list'            // Lists & list items
import './outlet'          // Content outlets
import './image'           // Images
import './svg'             // Inline SVG graphics
import './button'          // Interactive buttons
import './link'            // Hyperlinks
import './forms'           // Form controls
import './video'           // Video embeds
import './slotInstance'    // Component slot instances
import './slotOutlet'      // Component slot outlets
import './visualComponentRef' // References to other visual components

Import order matters in this file because module IDs reference each other, and the entire registry is loaded only when the admin bundle requires it.

Standard Module Layout and File Conventions

Each first-party block module occupies its own subfolder under src/modules/base/ and follows a strict file naming convention. This structure allows the system to locate TypeBox schemas, React editors, and styles predictably.

  • index.ts – Exports the module’s public API and executes the registerModule() call.
  • props.ts – Contains the TypeBox schema defining the module’s configurable properties.
  • *Editor.tsx (e.g., ButtonEditor.tsx) – React component rendered in the visual editor for property editing.
  • *Editor.module.css (optional) – CSS module providing isolated styling for the editor UI.
  • __tests__/ (optional) – Unit tests covering rendering logic and property validation.
  • placeholder.ts or svgCanvasRoot.ts – Media-specific helpers for generating placeholder content or canvas roots.

All modules adhere to this pattern, making the codebase navigable and tooling-friendly.

Category-Based Organization

Instatic groups first-party block modules by functional category, reflected in both folder naming and the import sequence in src/modules/base/index.ts:

This categorization ensures that related blocks are registered in logical sequence, preventing dependency errors during initialization.

The Self-Registration Pattern with registerModule

Every module executes its registration inside its local index.ts by importing registerModule from @core/module-registry. This side-effect-based approach eliminates manual registry maintenance.

// src/modules/base/button/index.ts
import { registerModule } from '@core/module-registry';
import { Props } from './props';
import { ButtonEditor } from './ButtonEditor';

registerModule({
  id: 'button',
  name: 'Button',
  icon: 'button',
  props: Props,
  editor: ButtonEditor,
});

The registration object supplies:

  • id – Unique string identifier used throughout the editor.
  • name – Human-readable label appearing in the block picker.
  • icon – Reference to the pixel-art-icons set.
  • props – TypeBox schema for configuration.
  • editor – React component responsible for rendering the editing UI.

Once registered, the block automatically appears in the editor’s block picker without additional wiring.

Extending the System: Adding New First-Party Blocks

To add a new built-in block to Instatic, follow the established skeleton:

  1. Create a new folder under src/modules/base/ (e.g., myWidget).
  2. Define props.ts with a TypeBox schema describing configurable fields.
  3. Implement MyWidgetEditor.tsx as the React UI for editing properties.
  4. Create index.ts that imports the schema and editor, then calls registerModule().
  5. Import the new folder in src/modules/base/index.ts to include it in the lazy registration bundle.

Because registration relies on side-effects, the module becomes available immediately after the import is added, with no additional configuration required in the admin entry point.

Summary

  • Location: All first-party block modules reside in src/modules/base/.
  • Registration: Centralized via side-effect imports in src/modules/base/index.ts, loaded lazily by the admin bundle.
  • Structure: Each module contains index.ts (registration), props.ts (TypeBox schema), and an *Editor.tsx component.
  • Categories: Blocks are grouped logically (Layout, Typography, Media, Interactive, Form, Component system).
  • Extensibility: New blocks follow the same folder structure and self-register through registerModule() from @core/module-registry.

Frequently Asked Questions

What is the entry point for loading all first-party blocks in Instatic?

The file src/modules/base/index.ts serves as the central hub. It is imported lazily by src/admin/AdminEntry.tsx and triggers registration for every built-in block through side-effect imports like import './button'.

How does a block module register itself with the Instatic editor?

Each module calls registerModule() imported from @core/module-registry inside its local index.ts. This function accepts an object containing the module ID, display name, icon, TypeBox props schema, and React editor component, making the block available in the visual editor.

What files are required when creating a new first-party block module?

At minimum, you need three files: props.ts defining the TypeBox configuration schema, an *Editor.tsx React component for the editing interface, and index.ts to import both and invoke registerModule(). Optional files include *.module.css for styling and __tests__/ for unit tests.

Why does the import order matter in src/modules/base/index.ts?

Import order matters because some module IDs reference each other during registration. Loading dependencies in the correct sequence prevents undefined reference errors when the global module registry initializes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →