@onderwijsin/directus-markdown-editor-bundle
v0.7.0
Published
Directus Markdown/MDC editor interface with coordinated documentation seeding
Readme
@onderwijsin/directus-markdown-editor-bundle
A Markdown Editor with support for dynamic components (MDC) and AI-assisted editing - built with Tiptap. The bundle combines the Markdown Editor field interface with supporting extensions for schema management and AI server logic.
The editor stores portable Markdown in one text or string field. It does not store Tiptap JSON
and is agnostic of any specific frontend or rendering framework. The Markdown syntax is compatible
with the Comark specification, and uses both components and
attributes.
Features
- headings, paragraphs, marks, lists, blockquotes, dividers, links, tables, images, video, and syntax-highlighted code blocks;
- keyboard shortcuts, a searchable
/command menu, and other Notion-like editing features; - full-screen editing, and direct Markdown source editing;
- generic inline and block components with typed properties and named editable slots;
- static or remotely loaded component metadata with one definitive JSON contract for dynamic components;
- hydration-time component property freshness reports with editor-controlled refresh;
- permission-aware Directus item references with source snapshots and integrity reporting;
- opt-in AI transformations with reusable skills, custom prompts, document comparison, and stale-safe selection replacement;
- an optional Dutch Studio Docs article for editors.
Requirements
- Directus
>=12.2.0 <13; - Node.js
>=24.10.0when managing the package outside the official Directus image; - a trusted, self-hosted Directus runtime that permits non-sandboxed API extensions; and
- a
textorstringfield for every Markdown document.
The server hook makes this a non-sandboxed bundle. It is not suitable for Directus environments that permit only sandboxed extensions.
Installation
Install the package in the same runtime that starts Directus, then restart Directus:
pnpm add @onderwijsin/directus-markdown-editor-bundleFor a Docker deployment, build a Directus image containing the extension:
FROM directus/directus:12.2.0
USER root
RUN corepack enable
USER node
RUN pnpm add @onderwijsin/directus-markdown-editor-bundleservices:
directus:
build: .
environment:
MARKDOWN_EDITOR_ENABLED: 'true'
EDITOR_AI_PROVIDER: 'openai'
EDITOR_AI_MODEL: 'your-model-id'
EDITOR_AI_API_KEY: 'replace-with-a-secret'Pin the Directus image to a version supported by the package and use your normal image update
process to upgrade. Directus loads the markdown-editor-interface app entry and
markdown-editor-hook API entry from the installed bundle.
Quick start
- Open Settings → Data Model and choose a collection.
- Add a field with type Text (or String).
- Select the Markdown Editor interface.
- Leave Available editor tools empty for the first setup; an empty selection enables every tool.
- Save the field and open an item in the collection.
The API value remains a string:
{
"body": "# Welcome\n\nThis content was written in Directus."
}Interface configuration
Configure these options on each field using the Markdown Editor interface.
| Option | Default | Description |
| --------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Available editor tools | empty | Selects the toolbar, / menu, block actions, insertion controls, and native shortcuts available on this field. An empty selection enables every tool. If no paragraph or heading level is selected, paragraph remains available. |
| Store assets as | path | Format for selected Directus images in Markdown and component image properties: id, path (/assets/{id}), or url. Existing image values are not rewritten. |
| Asset base URL | unset | Required in url mode. HTTP(S) Directus base URL, for example https://directus.example.com; selected images are stored as https://directus.example.com/assets/{id}. |
| Use static component metadata | false | Chooses static JSON instead of loading component metadata from a URL. |
| Component metadata URL | unset | Browser-accessible JSONURL used while static metadata is disabled. |
| Static component metadata | unset | Required JSON value while static metadata is enabled. |
| Use item references | false | Enables the Reference picker, Reference editing, and document integrity checks. |
| Reference collections | unset | Required non-empty JSON array when item references are enabled. |
| Enable reference icon | false | Shows an Iconify picker in the Reference drawer when item references are enabled. Existing stored icons remain intact when disabled. |
| Iconify collections | all | Searchable collection selection for Reference icons. Leave empty to include every collection. Component icon properties use their own config. |
| Use Iconify API proxy | false | Uses /iconify for icons. Requires @onderwijsin/directus-iconify-bundle installed on this Directus instance; the editor does not provide the proxy. |
| Reference snapshot mode | detect | Chooses snapshot, detect, or sync behavior for source snapshots. |
| Enable AI editing | false | Shows AI actions for this field and allows authenticated /editor/ai requests for it. |
AI-assisted editing
AI is disabled per field by default. When enabled, AI is the first toolbar action. A non-empty
selection shows AI first in the selection toolbar, and the drag-handle menu offers the same actions
for its current block. The / menu starts with an AI section containing Write with AI and
skills with the insert scope. Document suggestions open a side-by-side Markdown diff and change
the field only after Apply changes. Selection suggestions open a floating review panel below the
target selection and replace it only after confirmation. Selected headings, lists, marks, and other
Markdown structure are included in the AI request. Insert suggestions are added to the document
immediately. None of these flows save the Directus item automatically.
The hook provisions the versioned editor_skills collection and the Can Use Editor Skills
policy. It also reconciles the bundled skill catalog during its own coordinated documentation phase.
Skill seeding is independent of the global DIRECTUS_EXTENSIONS_DATA_SEED_ENABLED gate, so the
catalog is still reconciled when ordinary policy and data seeding is disabled. New seeds are
created; changed seeds create or refresh Directus' Incoming version by default, so maintainer
edits to the main item are not overwritten. Set the strategy to override only when deployed seed
content must replace the main item. Assign the policy to editor roles that may use AI. It grants
create, read, and update access to skills, intentionally not delete access. Administrators can
always invoke the endpoint without this policy. The endpoint separately reads the selected skill
with request accountability, validates its scope and archive state, and never trusts a
client-supplied stored prompt.
| Environment variable | Default | Contract |
| ----------------------------------------- | ---------------- | ----------------------------------------------------------------------- |
| EDITOR_AI_PROVIDER | shared value | Local provider override: openai, anthropic, google, or mistral. |
| EDITOR_AI_MODEL | shared value | Local provider-native model override. |
| EDITOR_AI_API_KEY | shared/Directus | Local secret override used only by the API extension. |
| EDITOR_AI_BASE_URL | shared/default | Local provider-specific API base URL override. |
| DIRECTUS_EXTENSIONS_AI_PROVIDER | unset | Shared provider used when EDITOR_AI_PROVIDER is unset. |
| DIRECTUS_EXTENSIONS_AI_MODEL | unset | Shared model used when EDITOR_AI_MODEL is unset. |
| DIRECTUS_EXTENSIONS_AI_API_KEY | Directus setting | Shared secret used before a matching Directus provider credential. |
| DIRECTUS_EXTENSIONS_AI_BASE_URL | provider default | Shared provider API base URL. |
| EDITOR_AI_MAX_CONTENT_LENGTH | 100000 | Positive maximum request content length in characters. |
| MARKDOWN_EDITOR_SCHEMA_CHANGES_ENABLED | true | Provisions and reconciles editor_skills. |
| MARKDOWN_EDITOR_SCHEMA_ABORT_ON_ERROR | true | Aborts startup when that schema reconciliation fails. |
| MARKDOWN_EDITOR_SKILLS_SEED_ENABLED | true | Enables the hook-owned bundled skill seed catalog. |
| MARKDOWN_EDITOR_SKILLS_SEEDING_STRATEGY | versioning | Uses versioning or override for changed seeds. |
AI configuration resolves each value from the editor-specific variables, then the shared
DIRECTUS_EXTENSIONS_AI_* variables. When no API key is supplied, the endpoint reuses the encrypted
Directus project credential matching the selected OpenAI, Anthropic, or Google provider. The
provider and model must still be configured explicitly; Directus has no general default for either.
The internal settings read happens only after endpoint authorization and no credential is returned
to Studio. Mistral requires an editor-specific or shared API key.
editor_skills includes Directus versioning and the standard user_created, date_created,
user_updated, and date_updated audit fields. Bundled seeds use stable UUIDs. Removing a seed
from a later package release does not delete or archive an existing item.
The bundled catalog seeds Fix spelling and grammar, Improve clarity, Improve structure, Rewrite, Shorten, Expand, and Turn into bullet points. Their document/selection scopes control where each action appears.
POST /editor/ai requires an authenticated request with the Can Use Editor Skills policy,
collection/field context for a Markdown field whose ai option is true, and exactly one of
skillId or prompt. Stored skills must be readable, active, and compatible with scope
(document, selection, or insert). Insert requests supply the complete current Markdown with an
explicit insertion marker instead of replacement content. The response is
{ "content": "replacement or inserted Markdown" }. AI content and custom prompts are sent to the
configured provider; do not enable the feature for content that organizational policy forbids
sending there.
Requests also include a bounded, normalized description of the field's configured MDC components. This lets the model preserve component names, properties, and slots. The hardcoded system prompt is created per request from the target field's server-read Available editor tools, selected component metadata source, and Use item references configuration. It describes only syntax enabled for new authoring in that editor instance, while requiring existing syntax to be preserved. It also preserves every natural language used by the input unless the editing task explicitly requests another language, treats code as literal by default, and returns provider output without trimming intentional boundary whitespace.
Available editor tools can independently expose paragraphs, heading levels 1–6, bold, italic, strikethrough, inline code, blockquotes, code blocks, unordered and numbered lists, images, video, links, dividers, hard breaks, tables, clear formatting, history, source mode, and full-screen mode.
Component insertion is available whenever the selected metadata source provides components.
Reference insertion and integrity checks are controlled by Use item references. These features
are independent of Available editor tools. Saved component and reference tool IDs are
ignored.
Component metadata contract
Component metadata tells the editor which MDC components authors can insert, which properties they
can configure, and which named slots they can edit. The contract is independent of any frontend
framework and accepts either an array or an object with a components array:
{
"components": [
{
"name": "Callout",
"label": "Callout",
"description": "Highlight important supporting content.",
"nodeType": "block",
"props": {
"tone": {
"name": "Tone",
"type": "string",
"description": "Visual emphasis used by the website.",
"required": true,
"default": "info",
"values": ["info", "warning", "danger"]
},
"dismissible": {
"name": "Dismissible",
"type": "boolean",
"default": false
},
"audiences": {
"name": "Audiences",
"type": "array",
"values": ["students", "teachers", "parents"]
},
"image": {
"name": "Image",
"type": "string",
"tags": [{ "name": "specialInputType", "text": "image" }]
}
},
"slots": ["default"]
},
{
"name": "Icon",
"nodeType": "inline",
"props": [{ "name": "name", "type": "string", "required": true }],
"slots": []
}
]
}Component fields
| Field | Required | Contract |
| ------------- | -------- | --------------------------------------------------------------------------------------------------- |
| name | yes | Non-empty MDC component name. Reference is reserved and omitted from generic component insertion. |
| nodeType | yes | block or inline. Components with slots always insert as blocks. |
| label | no | Editor-facing name; defaults to name. |
| description | no | Editor-facing explanation shown during component selection. |
| tags | no | JSDoc tags; deprecated marks a supported component for replacement or removal. |
| props | no | Object keyed by property name, or an array whose entries contain name; defaults to {}. |
| slots | no | Array of slot-name strings or { "name": "..." } objects; defaults to []. |
Property fields
| Field | Required | Contract |
| ------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | only in array form | Property key in array form; optional editor-facing label in object form. |
| type | no | Editor input hint: string, number, boolean, object, or array. Unknown legacy values fall back to text. |
| description | no | Help text for the property. |
| required | no | Prevents insertion until the author supplies a value; defaults to false. |
| default | no | Initial JSON-compatible value applied during insertion. |
| values | no | Allowed string choices. On string this renders a select; on array it renders a multiselect. |
| properties | no | Child property map for an object, rendered as a labeled group. Children may themselves be objects or arrays. |
| items | no | Item definition for an array. Object items render a reorderable repeater; string items without values render editable tags. |
| tags | no | JSDoc tags from component metadata, with optional config; deprecated adds a hint, while specialInputType: image and specialInputType: url select richer controls. The former editor tag remains readable. |
The primitive controls preserve their corresponding values: string stores a string, number
stores a number, boolean stores a boolean, and a primitive array stores a string array. A string
property with values is the select form of the string control; select is not a separate property
type. Arrays without values accept free-form tags that can be dragged into order. Object arrays
render rows with Add, drag to reorder, keyboard move controls, and confirmed removal. Nested
required fields and URL hints block Apply/Insert when invalid. Objects and object arrays persist as
JSON-backed MDC attributes.
For example, a Hero can define image as
{ "type": "object", "properties": { "src": { "type": "string", "required": true, "tags": [{ "name": "specialInputType", "text": "image" }] } } }
and actions as
{ "type": "array", "items": { "type": "object", "properties": { "label": { "type": "string", "required": true }, "to": { "type": "string", "tags": [{ "name": "specialInputType", "text": "url" }] } } } }.
The icon hint renders an Iconify picker. Set its tag config to { "collections": ["lucide"] } to
limit that property to a collection. Without configured collections, the picker offers all
collections. This setting is separate from the editor's Iconify collections option for Reference
icons.
For an image asset property, keep the component prop typed as string and add the Vue-compatible
JSDoc tag @specialInputType image. In JSON this is
{ "name": "specialInputType", "text": "image" }. The drawer uses the Directus image selector,
shows the selected asset as a removable thumbnail, and persists the selected format from Store
assets as. The default is now /assets/{id} for newly selected MDC image properties; existing
stored IDs remain unchanged. Use @specialInputType url for a string control that requires a valid
HTTP(S) URL with its protocol. Existing metadata with @editor image or @editor url still works;
when both tags are supplied, specialInputType wins. Arbitrary JSDoc tags remain available, while
the editor interprets only documented hints and deprecated.
When a static value or metadata URL loads successfully, the editor treats that metadata as authoritative and checks components after hydration. Missing required properties, required properties whose persisted value is empty, removed properties, and added or removed slots produce an attention report and warning styling in the canvas. Components absent from authoritative metadata use error styling and expose deletion as their only action. A missing or failed metadata source is not interpreted as component deletion.
The scan never rewrites Markdown. Review opens the component drawer. Refresh properties is shown in the header only for property drift; it preserves known values, applies defaults, and removes obsolete properties. Refresh slots preserves supported slot content, removes unsupported slots and their content, and adds empty current slots. Changes are persisted only after Apply, which is blocked while required values are empty. Optional property additions do not create attention items.
Components and properties carrying a standard JSDoc deprecated tag remain functional and display a
Deprecated hint. Deprecated components are labeled in both insertion menus and appear in the report
and canvas as warnings so authors can replace or remove them. A deprecated property remains a UI
hint only. Tag text is shown as migration guidance when supplied; no separate deprecated field
is used.
Additional component and property fields are retained by validation but are not otherwise interpreted by the current editor. Refreshing newly added slots converts a legacy inline occurrence to a block because inline MDC cannot contain slots; other metadata changes preserve its node type.
When using Component metadata URL, the Directus user’s browser fetches the URL. Serve valid JSON
over HTTPS with CORS headers that allow the Studio origin. Authentication headers are not added by
the editor. Add the remote metadata host to Directus’s CSP connect-src directive with
CONTENT_SECURITY_POLICY_DIRECTIVES__CONNECT_SRC (see
Directus CSP configuration). How a
frontend generates, publishes, or transports this metadata is intentionally outside this package;
static JSON and the URL are equivalent inputs to the same contract.
MDC storage examples
Bare inline syntax such as :Icon is recognized only when currently loaded metadata identifies that
name as an inline component. Ordinary colon text such as 14:00 uur or list:with content stays
text. Explicit inline syntax (:Name{} or :Name{prop="value"}) remains supported for unknown
components, including when metadata is loading or unavailable. Existing component nodes always
serialize independently of metadata. Unknown bare syntax remains text; use {} to make an unknown
component explicit. Empty inline nodes serialize with {} to keep adjacent text separate.
Inline components use one colon and block components use two:
Read the :Badge{tone="info"} before continuing.
::Callout{tone="warning"}
#default
Remember to publish your changes.
::Named slots remain editable regions:
::Hero
#title
Welcome to our website
#description
Supporting **Markdown** for the introduction.
::YAML properties are supported for block components:
::Hero
---
theme: dark
layout: wide
---
#default
Hero content
::String attributes preserve escaped quotes and backslashes. Shorthand booleans and dynamic JSON bindings preserve their value types. Dynamic bindings use single outer quotes, so JSON objects and arrays retain their standard double-quoted JSON syntax. Unknown component names remain generic MDC nodes so stored content is not tied to the current metadata list.
Item references
References let authors select Directus items without creating a relational field. They are inline MDC snapshots, not database relations.
Enable Use item references and configure direct fields only:
[
{
"collection": "articles",
"displayField": "title",
"searchFields": ["title", "slug"],
"dataFields": ["slug", "status", "category"]
},
{
"collection": "programs",
"displayField": "name",
"dataFields": ["slug", "type"]
}
]| Field | Default | Contract |
| -------------- | ---------------- | ---------------------------------------------------------------------------- |
| collection | required | Unique Directus collection name. |
| displayField | required | Direct field used as the stored source label. |
| searchFields | [displayField] | Direct string or text fields searched with case-insensitive containment. |
| dataFields | [] | Direct fields copied into the stored source snapshot. |
Changing this configuration triggers a new integrity scan. Removing a configured collection marks
its persisted References as Not configured without rewriting them. Changing displayField
compares stored labels with the newly selected field, while changing dataFields compares the
stored data object with the newly selected field set. In detect mode differences are reported as
outdated; sync updates available snapshots in one editor transaction; snapshot leaves snapshot
values unchecked. Invalid collection configuration is reported without deleting stored References.
Outdated and archived References use warning styling in the canvas; unconfigured, unavailable, and
unverifiable References use error styling.
The editor discovers the real primary key and requests only the configured projection plus the
archive field configured on the collection. Nested fields, wildcards, aliases, foreign keys, and
relation traversal are rejected. Duplicate collection entries are invalid. Give Studio authors
read access to the configured collection, primary key, display field, search fields, data fields,
and archive field.
Snapshot modes behave as follows:
| Mode | Behavior when a document loads |
| ---------- | ----------------------------------------------------------------------------------------------------------------- |
| snapshot | Verifies availability and archive state without reporting ordinary snapshot differences. |
| detect | Verifies sources and reports changed labels/data so the author can refresh them. |
| sync | Refreshes changed labels/data in one editor transaction; the item becomes dirty but is never saved automatically. |
A stored Reference looks like this:
:Reference{collection="articles" item="article-7" label="Becoming a teacher" text="this article"
icon="school" :data='{"slug":"becoming-a-teacher"}'}collection, item, label, and object data are required on new References. item can be a
string or finite number. label and data belong to the source snapshot. Optional text and
icon belong to the author and survive refresh, synchronization, and source replacement. Consumer
renderers should display text ?? label.
Archived items are omitted from search and reported as Archived when still readable. Missing items and permission-hidden items are both reported as Unavailable. The editor never uses an administrator token, changes the source item, saves the containing item automatically, or creates a reverse index.
Links
Authors can insert links from the toolbar, selection bubble, slash menu, or drag-handle insert menu.
The link drawer requires visible link text and offers URL, internal path, email, and phone types.
URL values require an http:// or https:// protocol, internal paths must start with /, and
email values must be valid addresses. Phone values are accepted as entered. Email and phone links
persist as standard mailto: and tel: hrefs.
Code blocks and media
Code blocks use Shiki’s light and dark GitHub themes. Authors can choose a supported language, add a filename/path, and mark a block as collapsible. Stored Markdown remains compatible with Nuxt Content-style fences:
```ts [app/nuxt.config.ts]
export default defineNuxtConfig({})
```Collapsible code uses an MDC ::code-collapse wrapper. Inside code blocks, Tab and Shift-Tab
indent/outdent and Enter preserves indentation.
Images and video can be selected from the Directus file library or entered as HTTP(S), relative, or
/assets/{id} URLs. Newly selected images follow Store assets as; id values are previewed
through the current Directus /assets/ path while their Markdown keeps the bare ID. Image library
browsing is filtered to image MIME types and video browsing to video MIME types. Selected images and
videos show a removable preview; video previews include native playback controls. Existing image and
video blocks expose Edit image or Edit video in their drag-handle action menu. The image
drawer also accepts optional alt text, which is stored in the image Markdown and can be edited
later. Videos are constrained to the editor or preview width while retaining their natural size when
smaller. Executable and data protocols are rejected. The extension does not transform images,
generate captions, or provide a frontend media renderer.
Resizable Markdown images
Drag the handle centered on an image's right edge to resize it while preserving its natural aspect ratio. The chosen pixel width survives saving and reopening through Comark-compatible attributes:
{width="420"}Only width is persisted; height remains automatic. Images without a width retain the responsive
full-width default. Both states are constrained to the editor container. Remove the width attribute
in source mode, or drag the handle back to the container boundary, to return to full width. A small
snap zone at the boundary shows a dashed outline of the full-width target during dragging. The image
follows the pointer until release; releasing inside the active zone clears the stored width and
animates to full width. Resizing away from full width has no snap or animation. The release
animation is disabled when reduced motion is preferred. Invalid, zero, or negative widths use the
default state. Directus asset IDs remain unchanged in Markdown and resolve to asset URLs for editor
previews. Frontend image components should honor the width attribute and use
max-width: 100%; height: auto; to avoid overflow on narrower screens. Comark supports this
attribute syntax without a custom plugin.
Studio Docs article
Install @onderwijsin/directus-studio-docs-bundle when editors should receive the bundled Dutch
guide in Studio:
pnpm add @onderwijsin/directus-studio-docs-bundleThe Studio Docs bundle provisions studio_docs and its policies. Assign its view or manage policy
to the appropriate roles. This bundle contributes the stable Editor article during the
documentation startup phase.
| Environment variable | Default | Description |
| --------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| MARKDOWN_EDITOR_ENABLED | true | Enables the Markdown Editor server hook and its documentation contribution. The browser interface remains registered. |
| MARKDOWN_EDITOR_DOCS_SEED_ENABLED | true | Enables contribution of the Editor article. |
| DIRECTUS_EXTENSIONS_LOCK_PROVIDER | SYNCHRONIZATION_STORE | Startup lock: memory, redis, or fs. Use a shared provider for multiple Directus processes. |
| DIRECTUS_EXTENSIONS_LOCK_REDIS_URL | resolved Directus Redis config | Optional Redis URL override for the startup lock. |
| DIRECTUS_EXTENSIONS_LOCK_FS_DIRECTORY | unset | Required with the fs provider; all processes must share the directory. |
| SYNCHRONIZATION_STORE | memory | Shared fallback store: memory or redis. |
The hook accepts the shared schema/data and rate-limiter environment values through the common
startup schema, but its documentation phase—including the bundled editor skill catalog—is
independent of the global schema and data gates. See the Studio Docs bundle configuration for
seeding strategy, schema provisioning, and policy controls. With its default versioning strategy,
changed article content is written to the incoming version for review rather than replacing the
published article.
Security and operational boundaries
- Install the bundle only in a trusted Directus runtime.
- Treat remote component metadata as trusted authoring configuration and serve it over HTTPS.
- Reference searches use the signed-in Studio user’s API session and Directus permissions.
- Source mode warns when applying Markdown would normalize or remove unsupported syntax and requires explicit confirmation.
- Frontend rendering, component registration, styling, content sanitization, and Reference resolution are responsibilities of the consuming application.
- The package does not generate component metadata, inspect a frontend project, provision content collections, assign roles, or save edited Directus items automatically.
Troubleshooting
| Symptom | Check |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Markdown Editor is missing | Confirm the package is installed in the Directus runtime, the app entry is enabled, and Directus was restarted. |
| Component insertion is missing | Provide valid metadata through the selected static or URL source. |
| Remote metadata fails | Check the browser console for the source URL and validation path, then verify HTTPS, CORS, and JSON validity. A successful HTTP response can still fail metadata validation. The endpoint receives no custom authentication header. |
| A Reference collection is disabled | Check collection uniqueness, direct field names, primary-key metadata, field types, and relational fields. |
| Authors cannot find a Reference item | Check their read permissions and whether the item is archived. Search starts after text is entered. |
| A Reference is unavailable | The source is missing or hidden by permissions; replace or remove it from the integrity report. |
| Updated Studio docs are not visible | Inspect the incoming content version when the Studio Docs seeding strategy is versioning. |
| Startup reports a lock skip/error | Configure Redis or a shared filesystem lock for multi-process deployments. |
Default image alt text
For new image insertions, selecting a Directus file prefills alt text from its trimmed, non-empty
directus_files.description. If unavailable, the editor checks again after 500 ms, 1 s, 2 s, 4 s,
and 8 s, then stops silently. Waiting for image metadata… Save the image to continue in the
background. Stay on this page. shows with a small spinner during these checks; typing and saving
remain available. Metadata generation belongs to your project; the editor only reads descriptions
using the current Studio session. Allow authors read access to the selected file's id and
description for delayed descriptions to be available. Failed or denied reads leave insertion
available without a default.
Editing or clearing alt text makes the author's value authoritative for the rest of that insertion, even when another image is selected. Before any manual edit, selecting another image replaces the previous automatic description and invalidates pending checks. Deselecting an image or cancelling an unsaved insertion stops its checks. Saving the image transfers the lookup to that exact Markdown image occurrence: closing or unmounting the drawer then allows the bounded retries to continue while the editor remains mounted. A result fills the open field or updates the inserted image's alt text in the Markdown value, without saving the Directus item automatically. Stay on the page until the metadata resolves; leaving the page or destroying the editor cancels pending work. Deleting, undoing, replacing, or manually changing a pending image prevents a late description from updating it. Other images using the same file are unaffected. Existing images retain their serialized alt text when edited; opening their drawer does not start a new lookup. Reopening an image that still has its insertion lookup pending shows the waiting state and permits manual edits to cancel that default. Markdown image syntax remains unchanged.
Provider-bound configuration
Provider-sensitive inheritance is restricted to matching providers. A layer without a provider belongs to the provider inherited from lower layers; lower-layer models, API keys, and base URLs never acquire an overridden provider from above. When changing provider, supply a matching model and credentials in that layer, another matching layer, or provider-matched Directus settings. Unbound lower-layer credentials are not inherited.
An empty or whitespace-only DIRECTUS_EXTENSIONS_AI_API_KEY environment value is treated as unset,
allowing startup without an AI key and provider-matched credential fallback. Generation still
requires a complete resolved configuration.
License
MIT
