# Configure plugins

Choose the installation layer and distinguish options, document attributes, commands and runtime state.

Suppose your product only allows level-one and level-two headings. Heading's `levels` option determines which levels the editor accepts. Turning the current paragraph into a level-two heading uses the runtime `toggleHeading` command. These happen at different stages.

## Choose the installation layer

Check the Classic and Block statuses and required presets at the top of each reference. [Choose an editor and integration](/en/plugins/compatibility) explains those statuses independently of licensing.

| What you install | Where it belongs | Examples |
| --- | --- | --- |
| Nodes, marks, behavior and View registration | Core `extensions` | Heading, Link, Search, canvasViewExtension |
| A bundle of Core extensions | Core `extensions` | StarterKit, TextStyleKit |
| Product menus, sidebars and file services | Classic `plugins` | createClassicWordDocxPlugin, createClassicEditorPdfPlugin |
| A complete product assembly policy | Classic `preset` | createClassicWordPreset, createClassicWordProPreset |
| Block content and graphic workspace options | The `createBlockEditor` preset options object | starterKit, flowchart, mindMap |
| Additional Block capability arrays | Spread into `createBlockEditor` extensions | createBlockTableExtensions, createBlockNumberingExtensions |

An extension with Classic in its name may still be a Core Extension, such as ClassicWordTable. Follow each page's installation instructions rather than inferring the contract from its name.

## Configure a bundled plugin

StarterKit already includes Heading. Configure that child instead of adding another Heading.

```ts
import { StarterKit } from '@quirekit/starter-kit'

const extensions = [
  StarterKit.configure({ heading: { levels: [1, 2] } }),
]
```

Pass this array to Editor extensions. Parsing, commands and shortcuts accept only those two levels. Your toolbar should offer the same levels.

To supply a replacement with the same name, disable the preset child first. Only use `false` when the option type allows it.

```ts
import { StarterKit } from '@quirekit/starter-kit'
import { Heading } from '@quirekit/extension-heading'

const extensions = [
  StarterKit.configure({ heading: false }),
  Heading.configure({ levels: [1, 2] }),
]
```

Choose one approach. These are focused assembly changes; [Quick start](/en/guide/quick-start) explains the container and mounting lifecycle.

## Separate options from content

| Value | Lifetime | Example |
| --- | --- | --- |
| Extension options | Extension creation/assembly | Heading levels |
| Node and mark attributes | Canonical document, changed through commands | A heading's level, a link's href |
| Storage | Editor-local runtime state/readers | characterCount.characters() |
| Host services | Created and released by the application | Proofing provider, graphic workspace, transport |

`.configure()` returns a configured extension copy. It does not reconfigure an already mounted editor in place. Changing an options object also does not rewrite existing heading content; use commands to change the document.

```ts
editor.chain().focus().toggleHeading({ level: 2 }).run()
```

A `false` command result must not produce success feedback. For example, a Heading configured for levels one and two rejects `level: 3`. Connect `editor.can()`, `editor.isActive()` and transaction subscriptions to your controls; see [commands and controls](/en/build/commands).

## Try the configured behavior

This example accepts levels one and two. Apply level two, then try level three: the latter fails while preserving the document. Source files contain only this example's creation, controls and cleanup.

### plugins/configuration.vue

```vue
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { Editor } from "@quirekit/core";
import { StarterKit } from "@quirekit/starter-kit";
import { DomViewExtension } from "@quirekit/dom-view";
import { QuireButton } from "@quirekit/ui/vue";
import "@quirekit/pm-view/style/prosemirror.css";

const props = defineProps<{
  /** Interface language; changing it preserves the current document. */
  locale: "zh-CN" | "en";
}>();
const element = ref<HTMLElement>();
const result = ref<boolean | null>(null);
const copy = computed(() => props.locale === "en"
  ? { level2: "Apply heading 2", level3: "Try heading 3", undo: "Undo", initial: "This editor accepts headings 1 and 2.", idle: "Try the heading buttons.", success: "Accepted: document updated.", failure: "Rejected: level 3 is not allowed; content preserved.", label: "Configured heading example" }
  : { level2: "设为二级标题", level3: "尝试三级标题", undo: "撤销", initial: "这个编辑器只接受一、二级标题。", idle: "试试上方的标题按钮。", success: "操作成功，正文已更新。", failure: "操作被拒绝：不允许三级标题，正文保持不变。", label: "标题配置示例" });
let editor: Editor | undefined;

onMounted(() => {
  editor = new Editor({
    element: element.value,
    mode: "dom",
    extensions: [StarterKit.configure({ heading: { levels: [1, 2] } }), DomViewExtension],
    content: { type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: copy.value.initial }] }] },
  });
});
onBeforeUnmount(() => editor?.destroy());

/** @param level - Requested heading level. The configured extension rejects level three. */
function applyHeading(level: 2 | 3): void {
  if (editor) result.value = editor.chain().focus().setHeading({ level }).run();
}

/** Undoes the previous accepted edit through the same Core history. */
function undo(): void { editor?.chain().focus().undo().run(); result.value = null; }
</script>

<template>
  <div class="configured-plugin-example">
    <div class="configured-plugin-tools">
      <QuireButton variant="outline" @click="applyHeading(2)">{{ copy.level2 }}</QuireButton>
      <QuireButton variant="outline" @click="applyHeading(3)">{{ copy.level3 }}</QuireButton>
      <QuireButton variant="outline" @click="undo">{{ copy.undo }}</QuireButton>
    </div>
    <div ref="element" class="configured-plugin-document" role="group" :aria-label="copy.label" />
    <p role="status">{{ result === null ? copy.idle : result ? copy.success : copy.failure }}</p>
  </div>
</template>

<style scoped>
.configured-plugin-example { padding: 20px; }
.configured-plugin-tools { display: flex; flex-wrap: wrap; gap: 8px; }
.configured-plugin-document { border: 1px solid var(--qk-color-border-default); border-radius: 6px; padding: 20px; margin-top: 16px; }
.configured-plugin-document :deep(.ProseMirror) { min-height: 120px; line-height: 1.8; }
.configured-plugin-document :deep(h2) { font-size: 22px; margin: 0; }
.configured-plugin-example > p { font-size: 12px; color: var(--qk-color-text-secondary); margin-bottom: 0; }
</style>

```

## Read defaults in context

Each reference documents that entry's own defaults. A preset may override them: Proofing directly defaults provider to `null`, while the complete Word kit can choose a default proofing service. Read the assembly layer you actually use.

`undefined` means no explicit option; children or host assembly may still choose a fallback. StarterKit enables children by default, accepts an options object to configure a child, and disables it with `false`.

`null` often means an absent service, such as a graphic open port or proofing provider. It does not trigger an automatic network service. Each reference explains the ports it requires.

## Complete the integration

A document model does not automatically supply product UI or support every View and export format. Check the relevant View, required graphic/proofing/collaboration services, and persistence/export behavior.

Use [all plugins](/en/plugins) to find a capability. Follow [Build a product](/en/build) for complete assembly and [product plugin development](/en/build/product-plugin) for file-service integration.
