# Extension model and composition

Understand create, configure, extend, installation order and ownership.

## Choose the owner first

`Extension` supplies general behavior and presets; `Node` describes source nodes; `Mark` describes inline annotations. They share authoring hooks. They do not create a ProseMirror schema or a second JSON document.

| Goal | Entry point | Walkthrough |
| --- | --- | --- |
| Change defaults | `.configure()` | [Containers](/en/extensions/containers) |
| Keep syntax, replace controls | `.extend({ addNodeView() {} })` | [Code blocks](/en/extensions/code-blocks) |
| Introduce syntax with writable fields | `Node.create()` + `defineSource()` | [Custom nodes](/en/extensions/custom-node) |
| Add parsing rules | `addMarkdownExtensions()` | [Markdown plugins](/en/extensions/markdown-plugins) |
| Render multiline structural previews | `addProjectionLayers()` | [Diagrams](/en/extensions/diagrams) |
| Add transient state or input behavior | `Extension.create()` | [State](/en/guide/state), [input rules](/en/extensions/input-rules) |

## create: declare a reusable definition

~~~ts
import { Extension } from '@quirekit/msfe-core'

interface Options {
  /** Host application name used by this extension. */
  label: string
}
interface Storage {
  /** Accepted source changes observed in this installation. */
  changes: number
}
export const Audit = Extension.create<Options, Storage>({
  name: 'audit',
  addOptions() { return { label: 'editor' } },
  addStorage() { return { changes: 0 } },
  onUpdate() { this.storage.changes += 1 },
})
~~~

Definitions can be reused across editors. Options and storage are resolved independently per installation. Do not put instance state in module globals. Storage suits temporary services and counters; use `defineState()` for transaction mapping, history or projection dependencies.

## configure: change options

~~~ts
const ReportAudit = Audit.configure({ label: 'report' })
~~~

This returns a new definition. Plain records merge recursively; arrays replace entirely; functions and non-plain objects replace by value. Retain default container names explicitly when changing `types`. Configuration is not a mounted-instance hot update. Use instance methods for theme, mode and permission changes.

## extend: override a hook deliberately

~~~ts
const Audited = Audit.extend({
  onUpdate(event) {
    this.parent?.(event)
    console.debug(this.options.label, event.revision)
  },
})
~~~

Overridden hooks do not automatically combine results. Call the current hook's `this.parent?.()` to retain parent behavior and explicitly combine returned arrays. Use ordinary methods whenever a hook needs `this.options`, `this.storage`, `this.editor` or `this.parent`; arrow functions capture a different `this`.

Derived definitions inherit the name. Replace the corresponding preset entry instead of installing both definitions. Renaming alone does not resolve competing replacement projections.

## Install one preset

~~~ts
import { StarterKit } from '@quirekit/msfe-starter-kit'
import { CodeBlock } from '@quirekit/msfe-code-block'

const CustomCodeBlock = CodeBlock.extend({
  // Add the renderer from the code-block tutorial here.
})
const extensions = [
  StarterKit.configure({ codeBlock: CustomCodeBlock, image: false }),
  Audit,
]
~~~

Use `addExtensions()` to compose a business preset, then install that preset once. `requires` names extension dependencies. Dependency-ready extensions install in descending `priority`, default 100. Duplicate names and missing dependencies are diagnosed before mounting; array reordering should not conceal configuration errors.

## Lifecycle and ownership

| Phase | Available context | Appropriate work |
| --- | --- | --- |
| Configuration and state creation | Definitions, options and slots | Repeatable work without DOM effects |
| `onBeforeCreate` | Configuration and state, no mounted DOM | Logic independent of mounting |
| `onCreate` | Mounted editor | Subscribe to host services; optionally return a disposer |
| `onUpdate` | Accepted source changes | Incremental observation and dirty state |
| `onTransaction` | Every accepted transaction | Bounded reads, including selection/effects |
| `onSelectionUpdate` | A changed selection | Refresh dependent toolbar state |
| `onDestroy` | Disposal in progress | Cleanup in reverse installation order |

Components can unmount on scrolling, source reveal or mode changes. Their lifetime is shorter than the editor's. Bind [asynchronous resources](/en/extensions/resources) to the appropriate scope. See [Extension API](/en/api/extensions) for exact hook signatures.

## Earlier descriptors

`defineExtension()` and `defineNodeView()` remain public low-level entries. Their earlier descriptor configuration does not share all modern deep-merge semantics. Prefer the authoring API for new features. Tiptap-style names do not imply support for `parseHTML`, `renderHTML`, ProseMirror schema `content` or direct ProseMirror plugin installation.
