---
title: Typen-Referenz
description: Vollständige Referenz für @templatical/types — gemeinsame TypeScript-Typen, Block-Factories und Hilfsfunktionen.
---

# Typen-Referenz

`@templatical/types` stellt alle gemeinsamen TypeScript-Typen, Block-Factory-Funktionen und Hilfsfunktionen bereit.

::: code-group

```bash [npm]
npm install @templatical/types
```

```bash [pnpm]
pnpm add @templatical/types
```

```bash [yarn]
yarn add @templatical/types
```

```bash [bun]
bun add @templatical/types
```

:::

## Template-Struktur

### TemplateContent

Der Root-Typ, der ein E-Mail-Template repräsentiert.

```ts
interface TemplateContent {
  blocks: Block[];
  settings: TemplateSettings;
}
```

### TemplateSettings

```ts
interface TemplateSettings {
  width: number;                  // Template-Breite in Pixeln (Standard: 600)
  backgroundColor: string;        // Hintergrundfarbe
  textColor: string;              // Standard-Textfarbe; Textblöcke erben sie (Standard #1a1a1a)
  linkColor?: string;             // Linkfarbe; Links erben die Textfarbe, wenn nicht gesetzt
  linkUnderline: boolean;         // Textlinks unterstreichen (Standard true)
  fontFamily: string;             // Standard-Schriftfamilie
  preheaderText?: string;         // E-Mail-Preheader-Text
  locale: string;                 // BCP-47-Inhaltssprache; <html lang> (Standard 'en')
  locales?: string[];             // Weitere Sprachversionen; locale ist der Standard
  l10n?: Record<string, SettingsLocaleLayer>; // Sprachebenen der Einstellungen
  direction?: ContentDirection;   // 'ltr' | 'rtl'; ungesetzt folgt der Inhaltssprache
}

type ContentDirection = 'ltr' | 'rtl';
```

### Block

Eine diskriminierte Union aller Blocktypen:

```ts
type Block =
  | TitleBlock
  | ParagraphBlock
  | ImageBlock
  | ButtonBlock
  | SectionBlock
  | DividerBlock
  | VideoBlock
  | SpacerBlock
  | SocialIconsBlock
  | MenuBlock
  | TableBlock
  | HtmlBlock
  | CountdownBlock
  | CustomBlock
  | SlotBlock
  | WrapperBlock;
```

### BlockType

```ts
type BlockType =
  | 'title' | 'paragraph' | 'image' | 'button' | 'section'
  | 'divider' | 'video' | 'spacer' | 'social'
  | 'menu' | 'table' | 'html' | 'countdown' | 'custom'
  | 'slot' | 'wrapper';
```

## Lokalisierung {#localization}

Sprachversionen ergänzen optionale `BaseBlock.l10n`, `TemplateSettings.locales` und `TemplateSettings.l10n`. Die öffentlichen Deklarationen stammen aus `@templatical/types`; `definitions` ist standardmäßig `[]` zur Erkennung benutzerdefinierter Textfelder.

```ts
import type {
  Block,
  CustomBlockDefinition,
  TemplateContent,
  TemplateSettings,
} from "@templatical/types";

// doNotTranslate is editor/strings-helper config, not stored localization data.
export interface LocalizedText {
  value: string;
  /** `sourceHash(defaultValue)` when the value was written or kept. */
  sourceHash: string;
}

/** One language's layer on a block. Members are omitted when empty. */
export interface BlockLocaleLayer {
  /** Translated text, by field path. */
  text?: Record<string, LocalizedText>;
  /** Any other property overridden for this language, by field path. */
  set?: Record<string, unknown>;
  /** The block is left out of this language. */
  hidden?: true;
}

/** One language's layer on the template settings. */
export interface SettingsLocaleLayer {
  text?: Record<string, LocalizedText>;
  set?: Record<string, unknown>;
}

export declare const TRANSLATABLE_FIELDS: Readonly<
  Record<string, readonly string[]>
>;
export declare const SETTINGS_TRANSLATABLE_FIELDS: readonly string[];
export declare function translatableFields(
  block: Block,
  definitions?: readonly CustomBlockDefinition[],
): string[];
export declare function readPath(target: unknown, path: string): unknown;
export declare function sourceHash(value: string): string;
export declare function localizeBlock(
  block: Block,
  locale: string,
  defaultLocale: string,
): Block;
export declare function resolveLocale(
  content: TemplateContent,
  locale: string,
  options?: { missing?: "throw" | "default" },
): TemplateContent;
export interface LocaleCoverage {
  translated: string[];
  missing: string[];
  stale: string[];
}
export declare function localeCoverage(
  content: TemplateContent,
  locale: string,
  definitions?: readonly CustomBlockDefinition[],
): LocaleCoverage;
export declare function defaultLocaleOf(content: TemplateContent): string;
export declare function isLocaleTag(value: unknown): value is string;
export declare function isSafePath(path: string): boolean;
export declare function isTranslatablePath(
  blockType: string,
  path: string,
): boolean;
export declare function writePath(
  target: Record<string, unknown>,
  path: string,
  value: unknown,
): boolean;

export interface AddLocaleOperation {
  op: "addLocale";
  locale: string;
  /** Index in `settings.locales` to insert at; default the end. Undo uses it to restore order. */
  position?: number;
}

export interface RemoveLocaleOperation {
  op: "removeLocale";
  locale: string;
  /** Guard: the locale's block and settings layers must match exactly; absent means no layer. */
  expect?: {
    blocks: Record<string, BlockLocaleLayer>;
    settings?: SettingsLocaleLayer;
  };
}

export interface LocaleLayerExpect {
  text?: Record<string, LocalizedText | null>;
  /** `null` matches an absent override. */
  set?: Record<string, unknown>;
  hidden?: boolean;
}

export interface UpdateBlockLocaleOperation {
  op: "updateBlockLocale";
  blockId: string;
  locale: string;
  text?: Record<string, LocalizedText>;
  set?: Record<string, unknown>;
  unset?: string[];
  hidden?: boolean;
  expect?: LocaleLayerExpect;
}

export interface UpdateSettingsLocaleOperation {
  op: "updateSettingsLocale";
  locale: string;
  text?: Record<string, LocalizedText>;
  set?: Record<string, unknown>;
  unset?: string[];
  expect?: Omit<LocaleLayerExpect, "hidden">;
}

export declare function pruneLocaleOrphans(block: Block): Block;
export declare function pruneSettingsLocaleOrphans(
  settings: TemplateSettings,
): TemplateSettings;
export declare function isStructuralLocalePath(
  block: Block,
  path: string,
  value?: unknown,
): boolean;

/** Ids of blocks hidden in `locale`, depth first. Pure. */
export declare function localeHiddenBlocks(
  content: TemplateContent,
  locale: string,
): string[];
/** Field ids with a nonempty default that would keep the current default's value if `locale` became the default. Pure. */
export declare function defaultLocaleGaps(
  content: TemplateContent,
  locale: string,
  definitions?: readonly CustomBlockDefinition[],
): string[];
export interface SetDefaultLocaleOperation {
  op: "setDefaultLocale";
  /** A language already in `settings.locales`. */
  locale: string;
  /** Custom block ids mapped to registered text/textarea field paths, captured for replay. */
  customTextFields?: Record<string, string[]>;
}
```

`readPath` löst Arrayelemente über ihre ID auf und liefert `undefined` für einen nicht auflösbaren Pfad. `writePath` klont den Wert, behandelt `null` als Löschen der Eigenschaft und liefert `false`, wenn das übergeordnete Objekt fehlt. `isSafePath` lehnt leere und Prototyp-Schlüsselsegmente ab. `isLocaleTag` akzeptiert eine Sprache aus 2–3 Buchstaben mit optionalen alphanumerischen Untertags aus 2–8 Zeichen. `defaultLocaleOf` liefert `settings.locale`, mit Fallback auf `en`.

`TRANSLATABLE_FIELDS` listet die Textpfadmuster jedes Blocktyps, wobei `*` eine Element-ID oder bei benutzerdefinierten Blöcken einen Feldschlüssel bezeichnet: `content` von Titel, Absatz und HTML, `alt` von Bild und Video, `text` von Buttons, Countdown-Beschriftungen und `expiredMessage`, `items.*.text` bei Menüs, `rows.*.cells.*.content` bei Tabellen und `fieldValues.*` bei benutzerdefinierten Blöcken. `SETTINGS_TRANSLATABLE_FIELDS` ist `["preheaderText"]`. `translatableFields(block, definitions)` setzt die Muster eines Blocks in konkrete Pfade um und behält nur benutzerdefinierte Felder, deren Definitionstyp `text` oder `textarea` ist. Ein Locale-`text`-Eintrag muss einen dieser Pfade nennen; jede andere Überschreibung gehört in `set`.

`pruneLocaleOrphans(block: Block): Block` entfernt rekursiv nicht mehr auflösbare Textpfade und Set-Pfade ohne auflösbares Elternobjekt. `pruneSettingsLocaleOrphans(settings: TemplateSettings): TemplateSettings` wendet dieselbe Regel auf Einstellungen an. Beide sind rein und lassen leere Lokalisierungsfelder weg. `isStructuralLocalePath(block: Block, path: string, value?: unknown): boolean` erkennt Sektionsspalten, vollständige integrierte Listen und Element-IDs, die Locale-Overrides nicht ersetzen dürfen. Für die Prüfung eines vollständigen Elementersatzes den vorgesehenen `set`-Wert übergeben: Die ID muss unverändert bleiben, und eine Tabellenzeile muss ihre Zell-IDs in derselben Reihenfolge behalten. Custom-Overrides unter `fieldValues.*` sind ausgenommen.

`elementIdIssues(content: TemplateContent): { path: string; id: string }[]` meldet IDs von Menüeinträgen, Social-Icons, Tabellenzeilen und Tabellenzellen mit `.` oder `/` in Tiefensuche, einschließlich Abschnittsspalten und Layout-Wrappern. Der Helfer ist rein; Pfade verwenden Array-Indizes, etwa `blocks[2].items[0].id`. `validateTemplate` meldet jedes Problem als `<path>: Element id "<id>" must not contain "." or "/".`; `addBlock`, `updateBlock` und `setContent` lehnen eine solche ID, die sie einführen (in einem neuen Block oder neu in ihrem Block), mit `invalid_operation` und der `blockId` des zugehörigen Blocks ab; eine ID, die ihr Block schon hat, bleibt bearbeitbar.

`sourceHash` ist FNV-1a 32-Bit über UTF-8-Bytes, als acht kleingeschriebene Hexadezimalziffern: `""` → `811c9dc5`, `"a"` → `e40c292c`, `"foobar"` → `bf9cf968`. `localizeBlock` projiziert einen Block ohne Rekursion oder Ausfiltern ausgeblendeter Blöcke. `resolveLocale(content, locale, options?: { missing?: "throw" | "default" })` projiziert ein vollständiges Template oder Layout einschließlich Wrapper-Kindern, entfernt ausgeblendete Blöcke rekursiv und entfernt Lokalisierungsfelder. Ohne `missing` oder mit `missing: "throw"` wirft eine unbekannte Version `unknown_locale`. Mit `missing: "default"` wird die Standardsprache des Dokuments projiziert und dessen Standard-`settings.locale` beibehalten. Bekannte Versionen bleiben unverändert. Beide Helfer sind rein.

`localeCoverage` liefert Feld-IDs (`<blockId>/<path>` oder `settings/<path>`) in Dokumentreihenfolge, Einstellungen zuletzt. Fehlende nichtleere Standardwerte stehen in `missing`, Text mit geändertem `sourceHash` in `stale`, aktueller Text in `translated`. Ausgeblendete Blöcke und ihre Nachfahren sind ausgeschlossen. Benutzerdefinierte Definitionen erkennen `text`- und `textarea`-Felder. Verwenden Sie die Abdeckung für eine Nichtstandardversion; sie projiziert oder validiert keine Locale.

`addLocale`, `removeLocale`, `setDefaultLocale`, `updateBlockLocale` und `updateSettingsLocale` erweitern `Operation`. `addLocale.position` ist standardmäßig das Ende; Entfernen kann mit `expect` die gesamte Ebene prüfen. Updates wenden `unset`, `text`, `set`, dann Block-`hidden` an und verwenden die verschachtelte Form `LocaleLayerExpect`. Siehe [Lokalisierte Templates](/de/guide/localized-templates) für Bearbeitung, Feldpfade, Fallback und die Upgrade-Regel.

`SetDefaultLocaleOperation.customTextFields?: Record<string, string[]>` enthält registrierte benutzerdefinierte `text`-/`textarea`-Pfade nach Block-ID. Erstellen Sie die Operation mit `createSetDefaultLocaleOperation(content: TemplateContent, locale: string, definitions?: readonly CustomBlockDefinition[]): SetDefaultLocaleOperation` (`definitions` ist standardmäßig `[]`) und senden Sie sie unverändert an andere Sitzungen. Siehe [Standardsprache](/de/api/operations#standardsprache) für Validierung und Verhalten ohne Definitionen.

## Übersetzung {#translation}

`TranslationProvider` ist der unabhängige Vertrag für `init({ translation })`. `TranslationRequest`, `TranslationField`, `TranslationResult`, `TranslationErrorCode` und `LocaleString` sowie reine Helfer und Aufrufgrenzen werden aus `@templatical/types` exportiert:

```ts
import type {
  CustomBlockDefinition,
  Operation,
  SyntaxPreset,
  TemplateContent,
} from "@templatical/types";

export type TranslationErrorCode =
  "rate_limited" | "limit_reached" | "content_too_large" | "translation_failed";

export interface TranslationField {
  /** A block field id, or `settings/<path>`. */
  id: string;
  /** Merge and logic tags masked as numbered placeholders. */
  text: string;
  format: "html" | "text";
}

export interface TranslationRequest {
  templateId: string | null;
  sourceLocale: string;
  targetLocale: string;
  /** At most 50 fields and 20,000 characters of text per call. */
  fields: TranslationField[];
}

export interface TranslationResult {
  fields: { id: string; text: string }[];
}

/** A translation backend. Reject with an SdkError or, when aborted, an AbortError. */
export interface TranslationProvider {
  translate(
    request: TranslationRequest,
    signal: AbortSignal,
  ): Promise<TranslationResult>;
}

export const TRANSLATION_MAX_FIELDS = 50;
export const TRANSLATION_MAX_CHARS = 20_000;

export interface LocaleString {
  id: string;
  format: "html" | "text";
  /** The masked default value. */
  source: string;
  /** The hash of the unmasked default at extraction time. */
  sourceHash: string;
  /** The stored translation masked with the source's token ids. */
  text?: string;
}

export declare const TRANSLATABLE_HTML_FIELDS: Readonly<
  Record<string, readonly string[]>
>;

export declare function maskTags(
  text: string,
  syntax: SyntaxPreset,
  format: "html" | "text",
  options?: { doNotTranslate?: readonly string[] },
): { text: string; tokens: string[] };

export declare function unmaskTags(
  text: string,
  tokens: string[],
): string | null;

export declare function localeStrings(
  content: TemplateContent,
  locale: string,
  options: {
    definitions?: readonly CustomBlockDefinition[];
    syntax: SyntaxPreset;
    only: "gaps" | "all";
    blockId?: string;
    doNotTranslate?: readonly string[];
  },
): { strings: LocaleString[]; tokens: Record<string, string[]> };

export declare function applyLocaleStrings(
  content: TemplateContent,
  locale: string,
  values: { id: string; text: string; sourceHash: string }[],
  options: {
    definitions?: readonly CustomBlockDefinition[];
    syntax: SyntaxPreset;
    tokens: Record<string, string[]>;
    doNotTranslate?: readonly string[];
  },
): {
  operations: Operation[];
  rejected: {
    id: string;
    reason: "tags_changed" | "unknown_field" | "not_translatable";
  }[];
};
```

`TRANSLATION_MAX_FIELDS = 50` und `TRANSLATION_MAX_CHARS = 20_000` begrenzen jeden Aufruf. `TRANSLATABLE_HTML_FIELDS` ist `{ title: ["content"], paragraph: ["content"] }`; jedes andere geeignete Feld ist `text`. HTML-Blöcke bleiben von Hand übersetzbar und werden von `localeStrings` ausgelassen.

`maskTags(text, syntax, format, options?: { doNotTranslate?: readonly string[] })` maskiert ganze Chips nur in HTML und rohe Value-/Logik-Tags in beiden Formaten. Text-Tokens sind `<x id="n"/>`, Attribut-Tokens `__xn__`; beide teilen IDs ab 1 pro Feld in Quellreihenfolge. `unmaskTags(text, tokens)` stellt ursprüngliche Bytes nur bei exakter ID-Multimenge wieder her, andernfalls folgt `null`.

Übergeben Sie dasselbe `doNotTranslate?: readonly string[]` an `maskTags`, `localeStrings` und `applyLocaleStrings`, um konfigurierte Begriffe in Text und HTML-Attributwerten zu schützen. Begriffe werden getrimmt, unter Beachtung der Groß- und Kleinschreibung als ganze Wörter erkannt (Unicode-Buchstaben, Zahlen und `_` sind Wortzeichen), längste zuerst und ohne Überlappung; Tags und Chips haben Vorrang. Begriffe teilen die ID-Folge der Tags; die exakte Prüfung stellt jedes Vorkommen Byte für Byte wieder her oder lehnt es als `tags_changed` ab. Standardmäßig gibt es keine Begriffe. Siehe [Übersetzungsmaskierung](/de/backend/translation#provider-vertrag) für Limits und Editor-Konfiguration.

`localeStrings` verlangt explizit `only: "gaps" | "all"`; der Editor verwendet standardmäßig Lücken. Leere Standardwerte, überdeckende `set`-Überschreibungen und ausgeblendete Blöcke werden ausgelassen. `blockId` beschränkt die Auswahl auf diesen Block. Vorhandene Übersetzungen werden mit Quell-IDs maskiert. Allein die Abdeckung bestimmt Lücken (fehlend und veraltet) oder alle Felder (auch aktuelle). Lassen sich gespeicherte Tags oder geschützte Begriffe nicht mit den Tokens der aktuellen Quelle maskieren, enthält das Feld weiterhin den maskierten aktuellen Standard und `sourceHash`, jedoch kein `text`; die Übersetzung beginnt bei diesem aktuellen Standard. `sourceHash` stammt vom unmaskierten Standardwert zum Extraktionszeitpunkt.

`applyLocaleStrings` liefert Operationen, ohne sie anzuwenden: eine `updateBlockLocale` pro Block und höchstens eine `updateSettingsLocale`. Jede Übersetzung erhält den übergebenen Extraktionshash, keinen beim Anwenden neu berechneten Hash; ein veralteter Eintrag aus `localeStrings`, dessen `text` die gespeicherte Übersetzung ist, wird daher aktuell, wenn er unverändert angewendet wird. Überlappende `set`-Überschreibungen werden mit dem flachsten Pfad zuerst projiziert; tiefere Pfade haben Vorrang. Ablehnungsgründe sind `tags_changed`, `unknown_field` und `not_translatable`. Die Helfer bereinigen HTML nicht. Siehe [JSON-Rundlauf](/de/guide/localized-templates#translating) und [Provider-Vertrag](/de/backend/translation).

## HTML-URL-Richtlinie

`SAFE_LINK_SCHEMES` enthält die vom Editor akzeptierten Schemata. `DANGEROUS_SCRIPT_SCHEMES` enthält die vom Link-Linter gemeldeten Schemata für Skripte oder beliebige Nutzdaten. `normalizeUrlForSchemeCheck(value: string): string` entfernt Tab, LF und CR, führende Zeichen U+0000–U+0020 und nachgestellte Leerraumzeichen. `extractUrlScheme(value: string): string | null` liefert das normalisierte Schema in Kleinbuchstaben oder `null` für relative URLs und Fragmente. `DATA_IMAGE_SRC_PATTERN` erkennt die in `src` akzeptierten Bilddaten-MIME-Typen. `isSafeHtmlUrl(value: string, attribute: string): boolean` wendet diese gemeinsame URL-Wertrichtlinie an.

`isSafeUrlAttribute(name: string, value: string): boolean` wendet `isSafeHtmlUrl` auf `HTML_URL_ATTRIBUTES` an, die gemeinsame Menge des Sanitizer-Hooks im Editor: `href`, `xlink:href`, `background`, `poster` und `src`. Übergeben Sie den Wert mit dekodierten Zeichenreferenzen, so wie ein Browser ihn liest; Attributnamen werden ohne Beachtung der Groß- und Kleinschreibung verglichen. Nach der Normalisierung akzeptieren diese Attribute einen leeren Wert, eine URL ohne Schema, ein `#`-Fragment oder die Schemata `http`, `https`, `mailto`, `tel`, `ftp`, `ftps`, `sms`, `xmpp` und `cid`. Nur `src` akzeptiert zusätzlich eine `data:image/`-URL vom Typ `png`, `jpeg`, `jpg`, `gif`, `webp` oder `svg+xml`, gefolgt von `;`. Andere Schemata ergeben `false`. Die gemeinsame Menge `HTML_FORBIDDEN_ATTRIBUTES` aus der Sanitizer-Konfiguration (`srcdoc`, `ping`, `formaction` und `action`) ergibt immer `false`, auch bei leeren Werten oder HTTPS-Werten. Andere Attribute ergeben `true`. Der DOMPurify-Sanitizer des Editors und `template-tools strings import` verwenden diese gemeinsame Richtlinie; der Helfer validiert keine Tags, `on*`-Handler oder weiteren DOMPurify-Regeln. Diese prüfen Aufrufer getrennt.

## Basistypen

### BaseBlock

Alle Blöcke erweitern dieses Interface.

```ts
interface BaseBlock {
  id: string;
  type: string;
  styles: BlockStyles;
  visibility?: BlockVisibility;
  displayCondition?: DisplayCondition;
  l10n?: Record<string, BlockLocaleLayer>;
}
```

### BlockStyles

```ts
interface BlockStyles {
  padding: SpacingValue;
  backgroundColor?: string;
}
```

### SpacingValue

```ts
interface SpacingValue {
  top: number;
  right: number;
  bottom: number;
  left: number;
}
```

### BorderValue

Ein Rahmen pro Seite, wie `SpacingValue`: Jede Seite hat eigene Breite, eigenen Stil und eigene Farbe; Breite `0` lässt die Seite weg. Sektionen, Bilder und Schaltflächen unterstützen ihn.

```ts
interface BorderSideValue {
  width: number;   // px; 0 = Seite nicht gezeichnet
  style: 'solid' | 'dashed' | 'dotted';
  color: string;
}

interface BorderValue {
  top: BorderSideValue;
  right: BorderSideValue;
  bottom: BorderSideValue;
  left: BorderSideValue;
}
```

### BorderRadiusValue

Ein Eckenradius in px: eine Zahl für alle Ecken oder einer pro Ecke. Sektionen, Sektions-Wrapper, Bilder und Schaltflächen unterstützen ihn.

```ts
type BorderRadiusValue =
  | number
  | { topLeft: number; topRight: number; bottomRight: number; bottomLeft: number };
```

### BlockVisibility

Steuert, auf welchen Viewports ein Block sichtbar ist.

```ts
interface BlockVisibility {
  desktop: boolean;
  mobile: boolean;
}
```

## Blocktypen

Detaillierte Beschreibungen der einzelnen Blocktypen finden Sie im [Blocks-Leitfaden](/de/guide/blocks).

### TitleBlock

```ts
interface TitleBlock extends BaseBlock {
  type: 'title';
  content: string;
  level: HeadingLevel;
  color?: string;
  textAlign: 'left' | 'center' | 'right';
  fontFamily?: string;
}

type HeadingLevel = 1 | 2 | 3 | 4;
```

### ParagraphBlock

```ts
interface ParagraphBlock extends BaseBlock {
  type: 'paragraph';
  content: string;
  paragraphSpacing?: number;
}
```

### ImageBlock

```ts
interface ImageBlock extends BaseBlock {
  type: 'image';
  src: string;
  alt: string;
  width: number | 'full';
  /** Ohne Angabe wird die Höhe aus der Breite abgeleitet, das Seitenverhältnis bleibt erhalten. */
  height?: number;
  /** Die eigene Größe der Bilddatei, für die Seitenverhältnis-Sperre. Wird nie gerendert. */
  naturalWidth?: number;
  naturalHeight?: number;
  align: 'left' | 'center' | 'right';
  /** Eckenradius in px. Ohne Angabe oder 0 bleiben die Ecken eckig. */
  borderRadius?: BorderRadiusValue;
  /** Ohne Angabe = kein Rahmen. */
  border?: BorderValue;
  linkUrl?: string;
  linkOpenInNewTab?: boolean;
  placeholderUrl?: string;
  decorative?: boolean;
}
```

### ButtonBlock

```ts
interface ButtonBlock extends BaseBlock {
  type: 'button';
  text: string;
  url: string;
  backgroundColor: string;
  textColor: string;
  borderRadius: BorderRadiusValue;
  /** Outline-Schaltfläche: backgroundColor 'transparent' (das Schlüsselwort) plus eine textColor. Ein leerer Hintergrund wird als dieses Schlüsselwort exportiert. */
  border?: BorderValue;
  fontSize: number;
  buttonPadding: SpacingValue;
  fontFamily?: string;
  openInNewTab?: boolean;
  width?: number | 'full';
  align: 'left' | 'center' | 'right';
}
```

### SectionBlock

Container für mehrspaltige Layouts.

```ts
interface SectionBlock extends BaseBlock {
  type: 'section';
  columns: ColumnLayout;
  children: Block[][];
  stackOnMobile?: boolean;
  borderRadius?: BorderRadiusValue;
  border?: BorderValue;
  backgroundImage?: BackgroundImage;
  hero?: SectionHero;
  wrapper?: SectionWrapper;
}

interface SectionWrapper {
  backgroundColor?: string;
  padding?: SpacingValue;
  borderRadius?: BorderRadiusValue;
  backgroundImage?: BackgroundImage;
}

type ColumnLayout = '1' | '2' | '3' | '2-1' | '1-2';

interface BackgroundImage {
  url: string;
  size: 'cover' | 'contain' | 'auto';
  position: {
    x: 'left' | 'center' | 'right';
    y: 'top' | 'center' | 'bottom';
  };
  repeat: boolean; // false: painted once
  naturalWidth?: number;   // the image file's own size in px; a hero's 'image' height keeps this shape
  naturalHeight?: number;
}

interface SectionHero {
  height: 'image' | number; // 'image': as tall as the image across the desktop box; a number: fixed px, padding included
  verticalAlign: 'top' | 'middle' | 'bottom';
}
```

### DividerBlock

```ts
/** "0%" bis "100%" */
type DividerPercentWidth = `${number}%`;

interface DividerBlock extends BaseBlock {
  type: 'divider';
  lineStyle: 'solid' | 'dashed' | 'dotted';
  color: string;
  thickness: number;
  width: number | 'full' | DividerPercentWidth;
}
```

### VideoBlock

Wird als verlinktes Thumbnail-Bild gerendert -- E-Mail-Clients unterstützen keine eingebettete Wiedergabe.

```ts
interface VideoBlock extends BaseBlock {
  type: 'video';
  url: string;
  openInNewTab?: boolean;
  thumbnailUrl: string;   // Wird aus einer YouTube-/Vimeo-URL abgeleitet, wenn leer
  alt: string;
  width: number | 'full';
  /** Ohne Angabe wird die Höhe aus der Breite abgeleitet, das Seitenverhältnis bleibt erhalten. */
  height?: number;
  align: 'left' | 'center' | 'right';
  placeholderUrl?: string;
}
```

### SpacerBlock

```ts
interface SpacerBlock extends BaseBlock {
  type: 'spacer';
  height: number;
}
```

### SocialIconsBlock

```ts
interface SocialIconsBlock extends BaseBlock {
  type: 'social';
  icons: SocialIcon[];
  iconStyle: SocialIconStyle;
  iconTone?: SocialIconTone; // ohne Angabe: 'brand'
  iconSize: SocialIconSize;
  spacing: number;
  align: 'left' | 'center' | 'right';
}

interface SocialIcon {
  id: string;
  platform: SocialPlatform;
  url: string;
}

type SocialPlatform =
  | 'facebook' | 'twitter' | 'instagram' | 'linkedin'
  | 'youtube' | 'tiktok' | 'pinterest' | 'email'
  | 'whatsapp' | 'telegram' | 'discord' | 'snapchat'
  | 'reddit' | 'github' | 'dribbble' | 'behance'
  | 'website';


type SocialIconStyle =
  | 'solid' | 'outlined' | 'rounded' | 'square' | 'circle' | 'plain';
type SocialIconTone = 'brand' | 'dark' | 'light';
type SocialIconSize = 'small' | 'medium' | 'large';
```

### MenuBlock

```ts
interface MenuBlock extends BaseBlock {
  type: 'menu';
  items: MenuItemData[];
  fontSize: number;
  fontFamily?: string;
  color: string;
  linkColor?: string;
  textAlign: 'left' | 'center' | 'right';
  separator: string;
  separatorColor: string;
  spacing: number;
}

interface MenuItemData {
  id: string;
  text: string;
  url: string;
  openInNewTab: boolean;
  bold: boolean;
  underline: boolean;
  color?: string;
}
```

### TableBlock

```ts
interface TableBlock extends BaseBlock {
  type: 'table';
  rows: TableRowData[];
  hasHeaderRow: boolean;
  headerBackgroundColor?: string;
  borderColor: string;
  borderWidth: number;
  cellPadding: number;
  fontSize: number;
  fontFamily?: string;
  color: string;
  textAlign: 'left' | 'center' | 'right';
}

interface TableRowData {
  id: string;
  cells: TableCellData[];
}

interface TableCellData {
  id: string;
  content: string;
}
```

### HtmlBlock

```ts
interface HtmlBlock extends BaseBlock {
  type: 'html';
  content: string;          // Rohes HTML
}
```

### CountdownBlock

Wird als animiertes GIF gerendert, was das Templatical-Cloud-Backend erfordert -- `@templatical/renderer` gibt stattdessen einen `templatical:unrenderable-block`-Marker aus. Siehe [Blöcke ohne Renderer](/de/backend/render#blocke-ohne-renderer).

```ts
interface CountdownBlock extends BaseBlock {
  type: 'countdown';
  targetDate: string;       // Lokales Datum/Uhrzeit, 'YYYY-MM-DDTHH:mm'
  timezone: string;         // IANA-Name, in dem das Ziel gelesen wird, z. B. 'Europe/Berlin'
  showDays: boolean;
  showHours: boolean;
  showMinutes: boolean;
  showSeconds: boolean;
  separator: ':' | '-' | ' ';
  digitFontSize: number;
  digitColor: string;
  labelColor: string;
  labelFontSize: number;
  backgroundColor: string;
  fontFamily?: string;
  labelDays: string;
  labelHours: string;
  labelMinutes: string;
  labelSeconds: string;
  expiredMessage: string;
  expiredImageUrl: string;  // Wird nach Ablauf statt des Timers angezeigt
  hideOnExpiry: boolean;
}
```

### CustomBlock

```ts
interface CustomBlock extends BaseBlock {
  type: 'custom';
  customType: string;
  fieldValues: Record<string, unknown>;
  renderedHtml?: string;
  renderedStylesheet?: string;
  dataSourceFetched?: boolean;
}

interface SlotBlock extends BaseBlock {
  type: 'slot';
}

interface WrapperBlock extends BaseBlock {
  type: 'wrapper';
  children: Block[];
  borderRadius?: number;
  backgroundImage?: BackgroundImage;
}
```

`slot` und `wrapper` sind Layout-Markierungen. Der Skill gibt sie nie aus. Siehe [Layout](/de/guide/layout).

## Konfigurationstypen

### MergeTag

```ts
interface MergeTag {
  label: string;
  value: string;
  group?: string;        // nur Picker: Gruppierungslabel
  description?: string;  // nur Picker: Hilfetext
  sample?: string;       // nur Vorschau: Beispielwert; nie im MJML
}

interface MergeTagRequestContext {
  reason: 'insert' | 'edit';
  current?: MergeTag;    // beim Bearbeiten, wenn das Token zu tags passt
}
```

### MergeTagsConfig

```ts
interface MergeTagsConfig {
  syntax?: SyntaxPresetName | SyntaxPreset;
  tags?: MergeTag[];
  onRequest?: (context?: MergeTagRequestContext) => Promise<MergeTag | null>;
  showRawValue?: boolean;   // Roh-Token im Tooltip anzeigen (Standard true)
  autocomplete?: boolean;   // tippbasiertes Autocomplete (Standard true)
}

type SyntaxPresetName = 'liquid' | 'handlebars' | 'mailchimp' | 'ampscript';
```

### DisplayCondition

```ts
interface DisplayCondition {
  label: string;
  before: string;
  after: string;
  group?: string;
  description?: string;
}
```

### DisplayConditionsConfig

```ts
interface DisplayConditionsConfig {
  conditions: DisplayCondition[];
  allowCustom?: boolean;
}
```

### CustomBlockDefinition

```ts
interface CustomBlockDefinition {
  type: string;
  name: string;
  icon?: string;
  description?: string;
  fields: CustomBlockField[];
  template: string;                       // Liquid-Template
  dataSource?: DataSourceConfig;
  defaultStyles?: Partial<BlockStyles>;   // Stile für neue Instanzen
  stylesheet?: string;                    // CSS, einmal in mj-head emittiert
}
```

Details zu den Feldtypen finden Sie unter [Benutzerdefinierte Blöcke](/de/guide/custom-blocks).

### ThemeOverrides

```ts
interface ThemeOverrides {
  bg?: string;
  bgElevated?: string;
  bgHover?: string;
  bgActive?: string;
  border?: string;
  borderLight?: string;
  text?: string;
  textMuted?: string;
  textDim?: string;
  primary?: string;
  primaryHover?: string;
  primaryLight?: string;
  secondary?: string;
  secondaryHover?: string;
  secondaryLight?: string;
  success?: string;
  successLight?: string;
  warning?: string;
  warningLight?: string;
  danger?: string;
  dangerLight?: string;
  canvasBg?: string;
  dark?: Omit<ThemeOverrides, 'dark'>;
}
```

### FontsConfig

```ts
interface FontsConfig {
  defaultFallback?: string;
  defaultFont?: string;
  customFonts?: CustomFont[];
  builtIns?: boolean | string[];  // true/weggelassen: alle sieben; false: keine; string[]: Allowlist — siehe /de/guide/fonts
}

interface CustomFont {
  name: string;
  url: string;
  fallback?: string;
}
```

### ViewportSize

```ts
type ViewportSize = 'desktop' | 'mobile';
```

## Factory-Funktionen

Die folgenden Block-Factory-Funktionen akzeptieren ein optionales Partial-Override-Objekt und geben einen vollständigen Block mit generierter ID zurück. `createCustomBlockLiquid(liquidjs)` wird ebenfalls für das Headless-Rendering benutzerdefinierter Blöcke exportiert; der [Leitfaden für benutzerdefinierte Blöcke](/de/guide/custom-blocks) beschreibt den Vertrag und den optionalen liquidjs-Peer.

Die öffentliche Modulstruktur der Factory hat keine Deklarationsabhängigkeit von `liquidjs`. Mit dem tatsächlichen Modul bleibt dessen `Liquid`-Instanztyp erhalten:

```ts
export interface CustomBlockLiquidModule {
  Liquid: new (...args: any[]) => any;
}

export declare function createCustomBlockLiquid<M extends CustomBlockLiquidModule>(module: M): InstanceType<M['Liquid']>;
```

```ts
import {
  createTitleBlock,
  createParagraphBlock,
  createImageBlock,
  createButtonBlock,
  createSectionBlock,
  createBackgroundImage,
  createDividerBlock,
  createVideoBlock,
  createSpacerBlock,
  createSocialIconsBlock,
  createMenuBlock,
  createTableBlock,
  createHtmlBlock,
  createCountdownBlock,
  createCustomBlock,
  createBlock,
  cloneBlock,
  createDefaultTemplateContent,
  generateId,
} from '@templatical/types';

// Countdown-GIFs brauchen Cloud oder ein `blockRenderers.countdown`-Override; der OSS-Renderer gibt einen Platzhalter aus.

// Mit Standardwerten erstellen
const paragraph = createParagraphBlock();

// Mit Überschreibungen erstellen
const heading = createTitleBlock({
  content: '<h1>Hello</h1>',
  level: 1,
});

// Beliebigen Block per Typ-String erstellen
const block = createBlock('button');

// Ein Hintergrundbild einer Sektion aus seiner URL: zentriert, cover, nicht gekachelt
const section = createSectionBlock({
  backgroundImage: createBackgroundImage('https://cdn.example.com/hero.jpg'),
});

// Deep Clone mit neuen IDs, auch für verschachtelte Zeilen, Einträge und Icons
const copy = cloneBlock(existingBlock);

// Leeres Template
const template = createDefaultTemplateContent();

// Eine UUID generieren
const id = generateId();
```

## Type Guards

```ts
import {
  isTitle, isParagraph, isImage, isButton, isSection,
  isDivider, isVideo, isSpacer, isSocialIcons,
  isMenu, isTable, isHtml, isCountdown, isCustomBlock,
} from '@templatical/types';

if (isTitle(block)) {
  console.log(block.level); // TypeScript weiß, dass dies ein TitleBlock ist
}
```

## EventEmitter

Ein typisierter Event-Emitter für Subscription-Muster.

```ts
import { EventEmitter } from '@templatical/types';

type Events = {
  change: TemplateContent;
  select: string | null;
};

const emitter = new EventEmitter<Events>();

const unsubscribe = emitter.on('change', (content) => {
  console.log('Changed:', content);
});

emitter.emit('change', templateContent);
unsubscribe(); // Listener entfernen
```

### Methoden

| Methode | Beschreibung |
|--------|-------------|
| `on(event, handler)` | Abonnieren. Gibt eine Unsubscribe-Funktion zurück |
| `off(event, handler)` | Einen bestimmten Handler abbestellen |
| `emit(event, data)` | Ein Ereignis auslösen |
| `removeAllListeners(event?)` | Alle Listener entfernen, optional für ein bestimmtes Ereignis |
| `listenerCount(event)` | Anzahl der Listener für ein Ereignis |

## Speicherverträge

`Template`, `TemplatePatch`, `SavedBlockInput`, `CommentsProvider`, `MediaProvider` und die übrigen BYO-Speicherformen stehen unter [Backend anbinden](/de/backend/). Diese Seite ist das Blockmodell und die Editor-Config.

## Operationen und Zusammenarbeit

`Operation`, einschließlich der Locale-Operationen, `BlockParent`, `OperationErrorCode`, `OperationError`, `applyOperations`, `applyOperationsInPlace` und `getColumnCount` beschreibt die [Operations-Referenz](/de/api/operations). Der Collaboration-Vertrag — `CollaborationProvider`, `CollaborationHandlers`, `OperationBatch`, `RevisionedBatch`, `CollaborationActor`, `CollaborationMember`, `BlockLock`, `BlockLockHolder`, `CollaborationConflict` und `CollaborationRemoteChange` — steht unter [Zusammenarbeit](/de/backend/collaboration). `Template` trägt `revision` und `locks` für einen Speicher, der zusammenarbeitet.

## KI

`AiProvider`, `AiConversationProvider`, die KI-Anfragen, Handler, Ergebnisse, `AiChatMessage` und `AiMergeTags` bilden den MIT-Provider-Vertrag in `@templatical/types`. [KI](/de/backend/ai#der-vertrag) beschreibt den Vertrag; die [KI-API-Referenz](/de/api/ai-kit) beschreibt HTTP-Client, Server-Kit und Protokoll.

`SdkError(message, statusCode?, code?, details?)` trägt KI-Fehlercodes und optionale `details`. `ThrottledError` erweitert ihn um `retryAfterMs`, Status 429 und den Code `rate_limited`. `invertOperations(content, ops)` liefert die abgesicherte Umkehrung eines Batches; es liegt in `@templatical/types` und wird von `@templatical/core` erneut exportiert.
