Module Schema Spec

Build your own
extraction modules.

Clipps modules are pure JSON config — schema, prompt, URLs. No code. Describe what you want extracted, paste the spec into your library, and Clipps's AI does the rest. Your AI assistant can author them for you.

Skip to schema Open module editor
Why it's safe

Config, not code.

A module is JSON. There is no eval, no vm, no scriptable hook. The runtime constraint surface is exactly three things: (a) what your extractionPrompt asks Gemini for, (b) what schema fields you declare in fields[], and (c) what URL templates you put in linkSources[]. A static safety scanner validates URLs (HTTPS only, no IP addresses, no lookalike domains), schema bounds (max 50 fields, max 100 enum values, prompt under 2KB), and field naming (no PII-implying names like credit_card_number). User-installed modules execute in a sandbox-by-construction model — the runtime simply has nowhere unsafe to send them.

The schema

CustomModule

The full top-level shape. Required fields are marked. Optional fields are safe to omit.

name string Required
Human-readable module name. Shown in module pickers and labels. e.g., "Vintage Watches".
slug string Required
URL-safe identifier. Lowercase, hyphenated, no spaces. Used as the module's stable id across the app and in moduleData on saved videos. e.g., "vintage-watches".
icon string Required
A single emoji or short string used as the module's visual marker. e.g., "⌚".
description string Required
One-sentence explanation of what the module does and what it extracts. Used in pickers and on the module store. e.g., "Identify and catalog vintage watches in saved media."
fields ModuleField[] Required
The structured shape of the data the module extracts. See ModuleField below. Maximum 50 fields.
extractionPrompt string Required
Plain-English instruction handed to Gemini at extraction time. Tell it exactly what to look for, how to handle ambiguity, and to output strict JSON matching the schema. Maximum 2KB.
linkSources LinkSource[] Required
URL templates that turn extracted fields into useful outbound links — search pages, marketplaces, references. See LinkSource below. Pass an empty array if the module is purely informational.
enableGoogleLens boolean Optional
When true, the runtime adds a Google Lens reverse-image lookup for the saved video's keyframe and merges results into extraction. Enable for visual modules (watches, sneakers, fashion). Defaults to false.
statusLabels ModuleStatusLabels Optional
Per-module labels for the want / active / done status UI on item cards. See ModuleStatusLabels. Omit entirely for utility modules; set all three values to null to suppress the status UI.
isBuiltin boolean Optional
Marks the module as ships-with-Clipps. AI-authored modules should leave this false or omit it. Reserved for first-party modules.
enabled boolean Optional
Whether the module is active for the user. The import flow sets this on installation. Defaults to true.

ModuleField

A single column in the extracted record. The runtime validates Gemini's output against this shape.

key string Required
Camel- or snake-case identifier used as the JSON key in the extracted object and inside linkSources[*].urlTemplate placeholders. e.g., "brand", "reference_number".
label string Required
Human-readable column header shown on item cards and in detail views. e.g., "Reference Number".
type "text" | "number" | "url" | "tags" | "enum" Required
The field's value shape. text is a free-form string. number is numeric and rendered with appropriate formatting. url is rendered as a link. tags is an array of short strings rendered as chips. enum is a string constrained to enumValues.
required boolean Optional
When true, the runtime drops the extraction if Gemini doesn't return a value for this field. Use sparingly — only for the field(s) without which the record is meaningless.
enumValues string[] Optional
Required when type === "enum". Maximum 100 values. Gemini is instructed to choose from this list. e.g., ["mint", "excellent", "good", "fair"].
description string Optional
Hint shown in the editor and used to enrich the prompt at extraction time. Useful for fields where the meaning is non-obvious.
role "title" | "subtitle" Optional
Marks which field acts as the primary display label for an item (title) or the detail appended to external search queries (subtitle). Exactly one field per module should be marked title; at most one subtitle. The frontend falls back to the first required field when these are omitted.

LinkSource

An outbound URL template. The runtime substitutes {fieldKey} placeholders with extracted values to produce a clickable link on every saved item.

name string Required
Display label for the link. e.g., "Chrono24", "Hodinkee".
urlTemplate string Required
HTTPS URL with {field} placeholders. Placeholders must reference keys that exist in fields[]. URL-encoding is applied automatically. e.g., "https://www.chrono24.com/search/index.htm?query={brand}+{model}".
icon string Optional
A single emoji shown next to the link. e.g., "🛒".
description string Optional
Tooltip / hint about what the linked destination contains.

ModuleStatusLabels

Per-module overrides for the three consumption states. Set any value to null to hide that state's button. Omit the whole object for utility modules where status doesn't apply.

want string | null Required
Label for the queued / wishlist state. e.g., "Want to read", "Want", "Saved".
active string | null Required
Label for the in-progress state. e.g., "Reading", "Have", "Playing".
done string | null Required
Label for the completed state. e.g., "Finished", "Sold", "Watched".
Worked example

A complete module

A real, ready-to-paste vintage-watches module. Copy the JSON, paste it into the Clipps module editor, and you have a working extractor.

{
  "name": "Vintage Watches",
  "slug": "vintage-watches",
  "icon": "⌚",
  "description": "Identify and catalog vintage watches in saved media. Pulls brand, model, reference number, year, and where to buy them.",
  "fields": [
    { "key": "brand",      "label": "Brand",            "type": "text",   "required": true, "role": "title" },
    { "key": "model",      "label": "Model",            "type": "text",   "required": true, "role": "subtitle" },
    { "key": "reference",  "label": "Reference Number", "type": "text" },
    { "key": "year",       "label": "Year",             "type": "number" },
    { "key": "movement",   "label": "Movement",         "type": "enum",   "enumValues": ["automatic", "manual", "quartz"] },
    { "key": "caseSizeMm", "label": "Case size (mm)",   "type": "number" },
    { "key": "condition",  "label": "Condition",        "type": "enum",   "enumValues": ["mint", "excellent", "good", "fair", "parts/repair"] }
  ],
  "extractionPrompt": "Identify any vintage watches visible in the content. For each watch, extract the brand (e.g. Rolex, Omega, Seiko), specific model name (e.g. Submariner, Speedmaster), reference number if shown, approximate decade or year, movement type (automatic/manual/quartz), case size in mm, and condition. If multiple watches are present, return only the most prominently featured one. Output strict JSON matching the schema. Do not infer details that aren't visible or stated.",
  "linkSources": [
    { "name": "Hodinkee",    "urlTemplate": "https://www.hodinkee.com/articles?q={brand}+{model}",                  "icon": "📰" },
    { "name": "Chrono24",    "urlTemplate": "https://www.chrono24.com/search/index.htm?query={brand}+{model}",       "icon": "🛒" },
    { "name": "WatchCharts", "urlTemplate": "https://watchcharts.com/listings/all/{brand}/{model}",                  "icon": "📊" },
    { "name": "eBay",        "urlTemplate": "https://www.ebay.com/sch/i.html?_nkw=vintage+{brand}+{model}",           "icon": "💰" }
  ],
  "enableGoogleLens": true,
  "statusLabels": { "want": "Want", "active": "Have", "done": "Sold" }
}
Prompt template best practices

Write a prompt Gemini can follow

A good extractionPrompt is the difference between sharp data and noise. Six rules.

Be specific about what to extract. "the most prominently featured watch" beats "watches".
Always end with "Output strict JSON matching the schema." so Gemini doesn't drift into prose.
Add "Do not infer details that aren't visible or stated." to cut hallucination.
Keep prompts under 1000 characters when possible. Hard ceiling is 2KB.
Reference field keys from your schema by name when ambiguity is possible.
Test against 3-5 saved videos before publishing. The runtime shows you the raw extraction so you can iterate.
For your AI assistant

Authoring on behalf of a user

Direct copy aimed at AI clients reading this page. Follow these steps verbatim.

If you're an AI assistant authoring a module on behalf of a user:

  1. Read the schema documentation above and the worked example.
  2. Generate a complete module JSON matching CustomModule.
  3. Validate before handing back: every required field present; every field.type is one of text | number | url | tags | enum; every linkSources[*].urlTemplate uses HTTPS and contains at least one {field} placeholder that maps to a key in fields[]; extractionPrompt is under 2KB; fields.length <= 50; any enum field has enumValues with at most 100 entries.
  4. Hand the JSON back to the user with a one-line explanation of what the module does.
  5. Tell the user to paste the JSON into Clipps web app → Settings → Modules → Import from JSON.

Use this template, fill in the placeholders, and only emit one module per response unless the user asks for several.

{
  "name": "{{name}}",
  "slug": "{{slug}}",
  "icon": "{{emoji}}",
  "description": "{{one-sentence description}}",
  "fields": [
    /* { "key": "...", "label": "...", "type": "text|number|url|tags|enum", "required": true|false, "role": "title|subtitle", "enumValues": ["..."] } */
  ],
  "extractionPrompt": "{{instruction to Gemini, ending with: Output strict JSON matching the schema. Do not infer details that aren't visible or stated.}}",
  "linkSources": [
    /* { "name": "...", "urlTemplate": "https://example.com/?q={key1}+{key2}", "icon": "..." } */
  ],
  "enableGoogleLens": false,
  "statusLabels": { "want": "Want", "active": "Have", "done": "Done" }
}
Validation rules

What the safety scanner checks

Every module — yours, your AI's, anyone's — runs through a static scanner before it can install or publish. These are the checks. Match them and your module ships immediately.

linkSources — HTTPS only, no IP addresses, no IDN homograph or lookalike domains, every {placeholder} resolves to a key in fields[].
extractionPrompt — under 2KB, no obvious jailbreak patterns (system-override phrases, role-impersonation, instruction smuggling).
fields — at most 50, at most 100 enumValues per enum field, no duplicate key values.
field naming — no PII-implying names like credit_card_number, ssn, password. Modules don't collect data the user hasn't already saved, but the scanner refuses to even shape a record around those names.
module naming — no trademark hijacking of major brands the user doesn't own, no profanity in name/slug/description.
FAQ

Questions, answered

How do I install a module I've authored?

Paste the JSON into Clipps web app → Settings → Modules → Import from JSON. The static scanner runs in the browser, and if it passes the module installs to your private library immediately.

Will my AI's module be reviewed?

For personal use only, no — the static scanner is the only gate, and (if it passes) the module installs immediately to your private library. To publish to the marketplace so other users can install it, an additional admin review step applies: the module enters a moduleSubmissions queue and a human at Clipps approves or rejects.

Can my AI test the module after authoring it?

Phase 3 of MCP development will support this — your AI will be able to dry-run a module against a saved video and inspect the extracted JSON. For now, paste the spec into the editor and try it on a test video yourself.

What's the difference between isBuiltin and a published module?

isBuiltin: true means the module ships with Clipps — first-party, written by us. AI-authored and user-authored modules should leave isBuiltin off. A published module (one approved into the public store) is still authored by a user and goes into publicModules/ — it's not promoted to built-in.

Why is extractionPrompt capped at 2KB?

To keep extraction-time latency and cost predictable, and because longer prompts tend to dilute the instruction. If you find yourself wanting more, split the module: one focused module per kind of thing the user wants to track.

What types can a field be?

One of text, number, url, tags, enum. That's the whole list. tags stores an array of short strings; everything else is a single value. enum requires an enumValues array.

How do {placeholders} work in urlTemplate?

Wrap a field key in curly braces. At render time, Clipps substitutes the extracted value (URL-encoded) into the template. e.g., {brand} in a Chrono24 search becomes Rolex. If a referenced key is missing from the extracted record, that link source is hidden for that item rather than producing a broken URL.