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

> Learn how Instatic structures first-party block modules under src/modules/base. Discover self-registration, TypeBox schemas, React editors, and CSS modules in this complete guide.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-08-01

---

**Instatic organizes first-party block modules under `src/modules/base/`, where each block self-registers via side-effect imports in [`index.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/index.ts)

The [`src/modules/base/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/index.ts) file serves as the central registration hub that [`src/admin/AdminEntry.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/AdminEntry.tsx) imports lazily. This file triggers side-effect registrations for every built-in block by importing each module folder directly:

```typescript
// 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`](https://github.com/CoreBunch/Instatic/blob/main/index.ts)** – Exports the module’s public API and executes the `registerModule()` call.
- **[`props.ts`](https://github.com/CoreBunch/Instatic/blob/main/props.ts)** – Contains the **TypeBox** schema defining the module’s configurable properties.
- **`*Editor.tsx`** (e.g., [`ButtonEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/placeholder.ts) or [`svgCanvasRoot.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/index.ts):

- **Page body**: `body` (e.g., [`BodyEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/BodyEditor.tsx))
- **Layout**: `container`, `loop` (e.g., [`ContainerEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/ContainerEditor.tsx), [`LoopEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/LoopEditor.tsx))
- **Typography**: `text`, `list`, `outlet` (e.g., [`TextEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/TextEditor.tsx), [`OutletEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/OutletEditor.tsx))
- **Media**: `image`, `svg`, `video` (e.g., [`ImageEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/ImageEditor.tsx), [`SvgEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/SvgEditor.tsx))
- **Interactive**: `button`, `link` (e.g., [`ButtonEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/ButtonEditor.tsx), [`LinkEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/LinkEditor.tsx))
- **Form**: `forms` (e.g., [`FormControls.tsx`](https://github.com/CoreBunch/Instatic/blob/main/FormControls.tsx))
- **Component system**: `slotInstance`, `slotOutlet`, `visualComponentRef` (e.g., [`SlotInstanceEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/SlotInstanceEditor.tsx))

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`](https://github.com/CoreBunch/Instatic/blob/main/index.ts) by importing `registerModule` from `@core/module-registry`. This side-effect-based approach eliminates manual registry maintenance.

```typescript
// 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`](https://github.com/CoreBunch/Instatic/blob/main/props.ts) with a TypeBox schema describing configurable fields.
3. Implement [`MyWidgetEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/MyWidgetEditor.tsx) as the React UI for editing properties.
4. Create [`index.ts`](https://github.com/CoreBunch/Instatic/blob/main/index.ts) that imports the schema and editor, then calls `registerModule()`.
5. Import the new folder in [`src/modules/base/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/index.ts), loaded lazily by the admin bundle.
- **Structure**: Each module contains [`index.ts`](https://github.com/CoreBunch/Instatic/blob/main/index.ts) (registration), [`props.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/index.ts) serves as the central hub. It is imported lazily by [`src/admin/AdminEntry.tsx`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/props.ts) defining the TypeBox configuration schema, an `*Editor.tsx` React component for the editing interface, and [`index.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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.