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.
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 full top-level shape. Required fields are marked. Optional fields are safe to omit.
"Vintage Watches".moduleData on saved videos. e.g., "vintage-watches"."⌚"."Identify and catalog vintage watches in saved media."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.null to suppress the status UI.false or omit it. Reserved for first-party modules.true.ModuleFieldA single column in the extracted record. The runtime validates Gemini's output against this shape.
linkSources[*].urlTemplate placeholders. e.g., "brand", "reference_number"."Reference Number".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.
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.type === "enum". Maximum 100 values. Gemini is instructed to choose from this list. e.g., ["mint", "excellent", "good", "fair"].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.LinkSourceAn outbound URL template. The runtime substitutes {fieldKey} placeholders with extracted values to produce a clickable link on every saved item.
"Chrono24", "Hodinkee".{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}"."🛒".ModuleStatusLabelsPer-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 to read", "Want", "Saved"."Reading", "Have", "Playing"."Finished", "Sold", "Watched".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" }
}
A good extractionPrompt is the difference between sharp data and noise. Six rules.
"the most prominently featured watch" beats "watches"."Output strict JSON matching the schema." so Gemini doesn't drift into prose."Do not infer details that aren't visible or stated." to cut hallucination.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:
CustomModule.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.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" }
}
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.
{placeholder} resolves to a key in fields[].enumValues per enum field, no duplicate key values.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.name/slug/description.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.
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.
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.
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.
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.
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.
{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.