# Write a behavior extension

Learn options, storage, commands and lifecycle through a small greeting command.

Suppose your product needs an “Insert greeting” button. It inserts ordinary text without introducing document structure, so use `Extension`. Start with an Editor containing StarterKit; complete the [quick start](/en/guide/quick-start) first if needed.

## 1. Separate configuration, runtime state and content

| Data | Owner | Saved in JSON? |
| --- | --- | --- |
| Greeting to insert | `addOptions()`, overridden through `.configure()` | No |
| Content update count for this session | `addStorage()` | No |
| Inserted text | Document produced by the command | Yes |

Persistent business data belongs in node or mark attributes, rather than storage. An extension object caches its storage. Create a fresh extension for each Editor to avoid sharing mutable state between editors.

## 2. Define a typed command

`createGreeting()` defines `insertGreeting()`. It delegates to `insertContent()` with a text node, so even `<b>Hello</b>` is literal text rather than interpreted HTML.

### core/behavior/greeting.ts

```ts
import { Extension } from "@quirekit/core";
import type { GreetingOptions, GreetingStorage } from "./types";

declare module "@quirekit/core" {
  /** Application commands installed by the greeting extension. */
  interface Commands<ReturnType> {
    /** Greeting behavior. */
    greeting: {
      /** @returns Whether insertion can run or was accepted. */
      insertGreeting: () => ReturnType;
    };
  }
}

/**
 * Creates fresh options/storage for each editor instead of sharing a mutable extension.
 * @param text - Literal greeting, never interpreted as HTML.
 * @returns One installable, configured behavior.
 */
export function createGreeting(text = "Hello!") {
  return Extension.create<GreetingOptions, GreetingStorage>({
    name: "greeting",
    /** @returns Application defaults. */
    addOptions() { return { text: "Hello!" }; },
    /** @returns Session-local state; it is not document JSON. */
    addStorage() { return { updates: 0 }; },
    /** @returns The same command for direct, chain and can modes. */
    addCommands() {
      return {
        insertGreeting: () => ({ commands }) => this.options.text.length > 0
          && commands.insertContent({ type: "text", text: this.options.text }),
      };
    },
    /** Counts accepted document updates; querying can() does not run this hook. */
    onUpdate() { this.storage.updates += 1; },
  }).configure({ text });
}

```

### core/behavior/types.ts

```ts
/** Configuration belongs to the application, not to the document. */
export interface GreetingOptions {
  /** Literal text inserted by the greeting command. */
  text: string;
}

/** Runtime counters belong to one configured extension instance. */
export interface GreetingStorage {
  /** Accepted content update notifications, excluding dry runs. */
  updates: number;
}

```

The module augmentation adds the same command type to `editor.commands`, `chain()` and `can()`. Types alone do not install behavior: the extension must be present at runtime.

## 3. Install and call it

```ts [Add to the Editor's extensions]
extensions: [StarterKit, createGreeting('Hello!')]
```

On a button click, call `editor.chain().focus().insertGreeting().run()`. Query availability with `editor.can().insertGreeting()`. Focus does not establish permission; the application still checks whether the user may edit.

Commands must support `can()` without requests, storage mutations or DOM effects. This recipe delegates insertion to an existing command and counts accepted updates in `onUpdate`. Availability queries do not increase the counter.

## 4. Configuration and inheritance

`.configure({ text: 'Welcome' })` changes public options. `.extend({ ... })` changes the definition, for example a keyboard binding. When overriding a hook that still needs its inherited behavior, call that hook's `this.parent?.()`.

Install only the final extension. Installing both the original and a same-name extended version causes name, Schema or command conflicts. Higher `priority` runs earlier; do not use ordering to conceal competing owners of one feature.

## 5. Verify the result

After `can()`, both content and storage should be unchanged. Executing the command inserts text; undo removes it. Two Editors with separate `createGreeting()` results keep separate counters. The counter never appears in downloaded JSON.

Compose complex commands through their supplied `tr`, `commands` and `chain`, without starting a separate `editor.commands` transaction inside them. A later false result does not roll back earlier writes; see [commands and transactions](/en/concepts/commands).

Continue with [input rules and state plugins](/en/guide/rules), or add persistent [nodes and layout](/en/build/extension). Consult the [extension API](/en/api/extensions) for complete hooks.
