Miraiclip SDK
Templates

Data & validation

Hydrate with data; bad data never renders.

hydrate checks a data row against the template's fields and returns a new, filled-in document. The template is not changed. Same template and same data always give the same document.

import { createProject } from "@miraiclip/core";
import { hydrate, type Template } from "@miraiclip/templates";

declare const template: Template; // the "Promo" template from Defining templates

const doc = hydrate(template, { name: "Ada", discount: 20 });
doc.clips.headline;        // text: "Hi Ada"
doc.clips.badge;           // params: { bg: "#ff8c32", size: 40, label: "20% off" }

const promo = createProject(doc); // preview, edit or export it like any project

accent and size weren't given, so their defaults apply. size stays a number because its param is exactly "{{size}}".

Asset fields

An asset field's value replaces the asset's src. Pass durationUs too when the new media's length differs.

import { hydrate, type Template } from "@miraiclip/templates";

declare const template: Template;

const doc = hydrate(template, {
  name: "Ada",
  discount: 20,
  footage: { src: "media/ada.mp4", durationUs: 6_000_000 },
});
doc.assets.bg; // { id: "bg", kind: "video", src: "media/ada.mp4", durationUs: 6000000 }

Clip timing doesn't change. If the new media is shorter than the clip using it, run fitClipsToMedia on the result.

Errors

hydrate throws TemplateValidationError. tryHydrate returns the problems instead, which suits forms and AI agents.

import { tryHydrate, type Template } from "@miraiclip/templates";

declare const template: Template;

const result = tryHydrate(template, { name: "A very long name that exceeds twenty", discount: 200, extra: 1 });
if (!result.ok) {
  result.issues;
  // [{ path: "data.extra", message: "unknown field" },
  //  { path: "data.name", message: "longer than maxLength 20" },
  //  { path: "data.discount", message: "above max 90" }]
}
ResultShape
Success{ ok: true, doc }
Failure{ ok: false, issues: [{ path, message }] }

Every problem in the row is reported at once. Paths start with data. and name the field.

MessageCause
required field is missingA required field without a default was left out
unknown fieldThe row has a key that isn't a field
expected a string, got numberWrong type (also a number, a boolean)
longer than maxLength 20text maxLength
does not match pattern ^[A-Z]text pattern
must be one of: Pro, Teamtext enum
below min 5, above max 90, expected an integernumber limits
expected a CSS color stringcolor is not a non-empty string
expected a source string or { src, durationUs? }asset value

Check without hydrating

validateData returns the issues plus the resolved values, with defaults applied.

import { validateData, type Template } from "@miraiclip/templates";

declare const template: Template;

const { resolved, issues } = validateData(template, { name: "Ada", discount: 20 });
Object.fromEntries(resolved); // { name: "Ada", accent: "#ff8c32", size: 40, discount: 20 }
issues;                       // []

Rows from CSV and NDJSON

For batches, turn text into rows. Both functions are pure: pass the file contents, not a path.

import { rowsFromCsv, rowsFromNdjson, type Template } from "@miraiclip/templates";

declare const template: Template;

rowsFromCsv(template, "name,discount,accent\nAda,20,\nGrace,35,#2e8f63\nLinus,abc,\n");
// [{ name: "Ada", discount: 20 },
//  { name: "Grace", discount: 35, accent: "#2e8f63" },
//  { name: "Linus", discount: NaN }]

rowsFromNdjson('{"name":"Ada","discount":20}\n\n{"name":"Grace","discount":35}\n');
// [{ name: "Ada", discount: 20 }, { name: "Grace", discount: 35 }]

CSV cells are strings, so rowsFromCsv converts them using the template's field types:

CellBecomes
EmptyLeft out, so the field's default applies
number fieldNumber(cell). Text that isn't a number becomes NaN, which fails validation
boolean fieldtrue for true or 1, false for anything else
Any other fieldThe string as-is

The first line is the header. Cells may be quoted with ", and "" inside quotes is a literal quote. Blank lines are skipped.

rowsFromNdjson parses one JSON object per non-empty line. Its values aren't converted.

Rows aren't validated when parsed. Validation happens per row in hydrate, tryHydrate or batch rendering.

As an AI tool

toFieldToolDefinition turns the fields into one tool definition. The model's tool input is exactly the data hydrate takes.

import { describeTemplate, toFieldToolDefinition, tryHydrate, type Template } from "@miraiclip/templates";

declare const template: Template;

const tool = toFieldToolDefinition(template);                       // { name, description, input_schema }
const openaiTool = toFieldToolDefinition(template, { style: "openai" }); // { type: "function", function: { … } }
const context = describeTemplate(template);                          // for the system prompt

// When the model calls the tool:
declare const input: Record<string, string | number | boolean>;
const result = tryHydrate(template, input);
// on failure, send result.issues back as the tool result so the model can fix its input

The generated schema for the "Promo" template, abridged:

{
  "name": "render_promo",
  "description": "Promo card Fill the \"Promo\" video template's fields; the values hydrate the template into a renderable document.",
  "input_schema": {
    "type": "object",
    "properties": {
      "name": { "type": "string", "maxLength": 20 },
      "accent": { "type": "string", "description": "A CSS color (e.g. \"#ff8c32\").", "default": "#ff8c32" },
      "size": { "type": "integer", "minimum": 24, "maximum": 96, "default": 40 },
      "discount": { "type": "integer", "minimum": 5, "maximum": 90 },
      "footage": { "description": "Replaces asset \"bg\": a source URL/path, or { src, durationUs? }.", "anyOf": ["…"] }
    },
    "required": ["name", "discount"],
    "additionalProperties": false
  }
}
OptionDefaultWhat it does
style"anthropic""anthropic" or "openai" wire shape
name"render_" + the template name, sanitizedTool name

For tools that edit any project, see Tools from the command catalog.

Insert into an existing project

To start a new project from a template, use createProject(hydrate(template, data)). To add a filled template to a project that already has content, such as an intro or an end card, use insertDocument.

import { fitClipsToMedia, hydrate, insertDocument, type Template } from "@miraiclip/templates";

declare const template: Template;

const doc = fitClipsToMedia(hydrate(template, { name: "Ada", discount: 20 }));
const plan = insertDocument(project, doc, { atUs: 4_000_000 }); // one undo step

plan.ids.clips.headline; // the inserted clip's id, e.g. "tpl-headline"
plan.startUs;            // 4000000
plan.endUs;              // where the inserted content ends
FunctionWhat it does
insertDocument(project, doc, options?)Adds the document's tracks on top of the existing ones, with clips shifted to atUs, as one transaction. Returns the plan
insertCommands(targetDoc, doc, options?)The same commands, without dispatching them
fitClipsToMedia(doc)Shortens video and audio clips that would play past the end of their media, and shortens or drops transitions that lose their footage. Returns a new document
insertDocument optionDefaultMeaning
atUs0Where the inserted document's time 0 lands
idPrefixderived to avoid collisionsPrefix for new clip, track and asset ids

Tracks without clips aren't inserted. The target's settings are not changed: compare doc.settings yourself if the frame size matters.

On this page