Build Your First Addon
This walkthrough builds a minimal governed addon called `example_notes`. It gives the addon one admin list/form workflow, one public route, registered permissions, a schema contract, a Menu Manager target provider, and an update path.
1. Create The Addon Folder
Create this package shape:
addons/example_notes/
manifest.php
README.md
bootstrap/install_contract.php
routes/routes.php
src/runtime.php
src/admin_runtime.php
src/public_runtime.php
src/schema_contract.php
src/permissions_contract.php
src/public_targets.php
languages/en_US/admin.php
languages/en_US/public.php
tests/smoke.phpKeep the folder name and `addon_key` identical: `example_notes`.
2. Write The Manifest
Start from Reference Samples/Sample Manifest.
Required decisions:
- stable `product_uuid`
- new `package_uuid`
- `addon_key`
- `display_name`
- `slug`
- `version`
- `addon_type`
- `summary`
- `discovery`
- `paths`
- `routes_contract`
- `admin_surface_contract`
- `frontend_contract`
- `risk_declarations`
See Addon Development/Manifest Contract for field rules.
3. Write The Install Contract
Create `bootstrap/install_contract.php`.
The install contract must declare:
- `addon_key`
- contract version
- whether schema contract is present
- whether permissions contract is present
- schema provider path and apply function
- permissions provider path or inline permissions
- install/update/rollback/uninstall declarations
See Addon Development/Install Contract.
4. Write The Schema Contract
Use a schema contract when the addon owns tables.
Example owned table:
CREATE TABLE IF NOT EXISTS amv_example_notes (
note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
note_uuid CHAR(36) NOT NULL,
title VARCHAR(190) NOT NULL,
slug VARCHAR(190) NOT NULL,
body_text LONGTEXT NOT NULL,
state_key VARCHAR(40) NOT NULL DEFAULT 'draft',
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (note_id),
UNIQUE KEY amv_example_notes_uuid_unique (note_uuid),
UNIQUE KEY amv_example_notes_slug_unique (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ciThe schema apply function should only create/update addon-owned schema.
5. Write The Permissions Contract
Create `src/permissions_contract.php`:
return [
'addon_key' => 'example_notes',
'permissions' => [
'example_notes.view' => [
'label' => 'View Example Notes',
'description' => 'View notes.',
],
'example_notes.manage' => [
'label' => 'Manage Example Notes',
'description' => 'Create, edit, publish, and archive notes.',
],
],
];Routes and admin saves must check these permissions.
6. Write Routes
Create `routes/routes.php`.
Expected routes:
- `GET /admin/index.php?view=example_notes`
- `POST /admin/index.php?view=example_notes`
- `GET /example-notes`
- `GET /example-notes/{slug}`
Use the Core route registry helpers already used by existing addons. Keep route declarations consistent with `routes_contract`.
7. Write Admin Runtime
The addon owns the inner admin content. The Admin shell owns the page frame.
Admin runtime should:
- require `admin.access` and `example_notes.view` for list screens
- require `example_notes.manage` for create/edit/save
- validate CSRF on POST
- use shared list/form helpers where possible
- return a notice after save
- redirect after successful POST to avoid duplicate submissions
See Developer Workflows/Admin Template Rules.
8. Write Public Runtime
Public runtime should:
- render published records only
- use public route params, not admin query strings
- return 404 for missing/unpublished records
- avoid requiring admin permissions for public pages
9. Write Public Target Provider
Create `src/public_targets.php`.
Expose:
- home target: `/example-notes`
- record targets: `/example-notes/{slug}`
- stable target identifiers
- labels and kind labels
- availability state
See Developer Workflows/Menu Manager Public Targets.
10. Package The Addon
Package the addon folder so `manifest.php` is at the package root for addon packages. Avoid unsafe paths, symlinks, absolute paths, hidden traversal, and oversized archives.
Package safety limits currently enforced by Installer include:
- archive max: 64 MB
- file max: 16 MB
- uncompressed total max: 96 MB
- file count max: 2048
- compression ratio max: 200
11. Install The Addon
Use Addon Installer for package intake.
Expected flow:
- Installer validates package safety.
- system governance validates identity, dependency, risk, route, slug, and route-key collisions.
- Installer deploys only the accepted package.
- Installer applies schema and permissions contracts.
- Installer syncs/hands off to system governance.
- system governance persists accepted lifecycle/runtime truth.
12. Update The Addon
For an update:
- keep `product_uuid` unchanged
- use a new `package_uuid`
- increment `version`
- keep `addon_key` stable
- keep route keys stable unless a migration explains the change
- include schema migrations in the schema/apply contract
An update is valid only when system governance confirms same lineage. Current Installer code still participates in some same-lineage checks; treat that as transitional drift.
13. Verify
Before release, verify:
- system governance sees the addon without issues.
- Addon Installer reports successful deployment.
- Schema tables exist.
- Permissions are registered.
- Admin list renders.
- Admin create/edit POST uses CSRF and redirects.
- Public list/detail routes return `200`.
- Missing public records return `404`.
- Menu Manager target provider lists home and record targets.
- Smoke test covers manifest, schema, permissions, routes, and public targets.
- Changelog entry exists for the release.
Contract Pages To Read
Use these pages while building:
- Addon Development/Manifest Contract
- Addon Development/Install Contract
- Addon Development/Schema Contract
- Addon Development/Permissions Contract Basics
- Developer Workflows/Admin Template Rules
- Reference Samples/Admin Runtime Sample
- Developer Workflows/Menu Manager Public Targets
- Hooks and Events/Practical Hook Examples
- Installer/Addon Update vs Clean Install
- Addon Development/Changelog Entry Rules
Updated: 2026-05-07 02:18:09