Skip to content

Templates

Viren070 edited this page Feb 28, 2026 · 22 revisions

A practical reference for writing AIOStreams configuration templates — covering all supported input types, dynamic expression syntax, and validation rules.


Table of Contents

  1. Template Structure
  2. metadata Fields
  3. Service Handling
  4. Template Inputs
  5. Dynamic Expressions
  6. Template Placeholders
  7. Validation Rules
  8. Sharing Templates

Template Structure

A template is a JSON object with two top-level keys:

{
  "metadata": { ... },
  "config": { ... }
}

config is a partial UserData object — only the keys you include are applied. Everything absent is left unchanged in the user's existing config. All dynamic expressions inside config are evaluated at load time, before the config is merged.


metadata Fields

Field Type Required Description
id string (1–100) No Unique identifier. Auto-generated UUID if omitted. Use namespaced form: author.my-template.
name string (1–100) Yes Display name in the template browser.
description string (1–1000) Yes Supports Markdown (links, bold, lists).
author string (1–20) Yes Author name or handle.
source "builtin" | "custom" | "external" No Defaults to "builtin". Use "external" for user-imported remote templates.
version semver string No Defaults to "1.0.0". Used for auto-update comparisons.
category string (1–20) Yes Shown in the browser filter bar (e.g. "Debrid", "Usenet").
services ServiceId[] No Controls service selection screen — see Service Handling.
serviceRequired boolean No Whether a service must be selected before applying.
setToSaveInstallMenu boolean No Redirects the UI to the Save & Install menu after loading. Defaults to true.
sourceUrl URL string No Remote URL the template was fetched from — enables auto-update checks.
inputs InputDefinition[] No User-fillable options shown before loading. See Template Inputs.

Service Handling

The services field in metadata controls whether (and how) the user is asked to select a debrid service before the template is applied.

Scenario Behaviour
services not set All services are shown in the selection screen
services: [] Service selection is skipped entirely
services: ["realdebrid", "torbox", ...] Only the listed services are shown
Single service + serviceRequired: true Service selection is skipped; user is prompted for that service's credentials directly
serviceRequired absent or false A "Skip" button is shown on the service selection screen

At load time, the selected services are available in conditions and interpolation via services.<serviceId>.

The bare services reference (no service ID) is also supported in all expression contexts. It resolves to truthy when at least one service has been selected and falsy when none have — making it the canonical way to distinguish between debrid mode (a service is active) and P2P / no-service mode (the user skipped service selection). See __if and __switch for usage.


Template Inputs

Inputs are defined in metadata.inputs as an array of objects. They are shown to the user in a dialog before the template loads. Values are then accessible throughout config via inputs.<id>.

Input Fields

Field Type Required Description
id string Yes Identifier used in inputs.<id> references throughout the config.
name string Yes Label shown in the UI.
description string Yes Help text below the label. Supports Markdown.
type see Input Types Yes Controls the UI widget.
required boolean No Prevents proceeding if the field is empty.
default any No Pre-filled value. Previous imports also pre-fill remembered values.
options { value, label }[] For select / multi-select List of choices.
showInSimpleMode boolean No Set to false to hide in Simple (Noob) mode. Defaults to true.
advanced boolean No Shorthand for showInSimpleMode: false — hidden in Simple mode.
constraints { min?, max?, forceInUi? } No Min/max length (strings) or min/max value (numbers). forceInUi enforces in the widget.
__if string No Hides the input when the condition is false. Only services.<id> is supported. See Conditional Inputs.
intent see Alert For alert Controls the colour and icon of an alert banner. Defaults to info.
socials { id, url }[] For socials List of social links to render. See Socials for valid id values.

Input Types

Type Widget Notes
string Text input
password Masked input For API keys and credentials — value is not stored in plaintext client-side after import.
number Number input Returns a number; blank/unset returns undefined.
boolean Toggle Returns true or false.
select Dropdown Requires options. Returns the selected value string.
select-with-custom Dropdown + text Same as select but adds a "Custom" option revealing a free-text input.
multi-select Multi-select Requires options. Returns an array of selected value strings.
url URL text input Client-side format validation.
alert Styled banner Displays a coloured info/warning/error banner. Uses name as title, description as body (Markdown). Captures no value. Requires intent. See Alert.
socials Icon row Renders a horizontal row of social media icon links. Uses socials field. Captures no value. See Socials.
subsection Button → modal Groups related sub-inputs behind a button. See Subsections below.

Subsections

A subsection input renders as a button that opens a modal containing its subOptions. Each sub-option is itself a full InputDefinition. Values for sub-options are stored as a nested object and accessed with dot notation:

inputs.<subsectionId>.<subOptionId>

This applies in __if conditions, __switch references, and {{}} interpolation.

{
  "id": "proxy",
  "name": "Proxy Settings",
  "description": "Optional proxy configuration.",
  "type": "subsection",
  "required": false,
  "subOptions": [
    {
      "id": "url",
      "name": "Proxy URL",
      "description": "Full URL including protocol.",
      "type": "url",
      "required": false
    },
    {
      "id": "id",
      "name": "Proxy Service",
      "description": "The proxy service to use.",
      "type": "select",
      "required": false,
      "options": [{ "value": "custom", "label": "Custom" }]
    }
  ]
}

Used in config as {{inputs.proxy.url}} or "__if": "inputs.proxy.url".


Alert

An alert input renders a styled banner — useful for displaying important notes, warnings, or requirements to the user before they configure options. It captures no value.

{
  "id": "notice",
  "name": "Important",
  "description": "This template requires an active RealDebrid subscription.",
  "type": "alert",
  "intent": "warning"
}

The intent field controls the colour and icon:

Intent Appearance
info Blue, filled background
success Green, filled background
warning Orange, filled background
alert Red, filled background
info-basic Neutral card with blue icon
success-basic Neutral card with green icon
warning-basic Neutral card with orange icon
alert-basic Neutral card with red icon

Defaults to info when intent is omitted. The filled variants (info, warning, etc.) use a tinted background; the -basic variants use a neutral card with a coloured icon only — less visually intrusive.

Note

id is still required by the schema but is not displayed and has no effect on the rendered banner. required, default, and options are ignored for this type.


Socials

A socials input renders a horizontal row of social icon links. Use it to credit yourself or link to support pages at the end of the inputs dialog. It captures no value.

The socials field is an array of { id, url } objects. Supported id values:

website · github · discord · ko-fi · patreon · buymeacoffee · github-sponsors · donate

{
  "id": "credits",
  "name": "Credits",
  "description": "Support the author",
  "type": "socials",
  "socials": [
    { "id": "github",  "url": "https://github.com/yourname" },
    { "id": "ko-fi",   "url": "https://ko-fi.com/yourname" },
    { "id": "discord", "url": "https://discord.gg/invite" }
  ]
}

Note

name and description are required by the schema but are not rendered. required, default, options, and constraints are ignored for this type.

Dynamic Expressions

All expressions are evaluated at load time, after the user fills in inputs and selects services. The resolved config is then merged into the user's data.

Three mechanisms are available:

Conditional Inputs

Any input can include a "__if" field. If the condition evaluates to false, the input is hidden entirely from the inputs dialog — the user never sees it and its value is treated as unset.

Only services conditions are supported here — the bare services (any service selected) or services.<id> (specific service selected). This is intentional: services are selected in the step before template inputs are shown, so the service state is fully known by the time the inputs dialog renders. Cross-input conditions (inputs.*) are not supported.

{
  "id": "rdApiKey",
  "name": "Real-Debrid API Key",
  "description": "Your RealDebrid API key.",
  "type": "password",
  "required": true,
  "__if": "services.realdebrid"
}

Compound and / or / xor conditions and negation (!) all work, as long as every operand is a services.<id> reference:

{ "__if": "services.realdebrid or services.alldebrid" }
{ "__if": "!services.torbox" }

Note

Inputs hidden by __if are excluded from required-field validation - a hidden required input will not block the user from proceeding.


{{}} — String Interpolation

Use {{inputs.<id>}}, {{services.<id>}}, or {{services.<id>.<key>}} inside any string value to substitute values at load time.

{
  "addonLogo": "{{inputs.logoUrl}}",
  "preferredLanguages": ["{{inputs.languages}}", "Original", "Unknown"]
}

Type preservation: When the entire string is a single {{...}} token, the raw value is returned — not stringified. This means an array input used as "{{inputs.languages}}" inside a preferredLanguages array will be spread out into the parent array, not inserted as a nested array. A boolean stays a boolean. A number stays a number.

"preferredLanguages": [
  "{{inputs.languages}}",
  "Original",
  "Dual Audio",
  "Multi",
  "Dubbed",
  "Unknown"
]

If inputs.languages is ["French", "German"], this resolves to:

["French", "German", "Original", "Dual Audio", "Multi", "Dubbed", "Unknown"]

Multiple tokens in one string: Concatenation mode — values are stringified. Useful for building dynamic expressions or comments:

"expression": "/*{{inputs.languagePassthrough}} Passthrough*/ passthrough(slice(language(..., '{{inputs.languagePassthrough}}'), 0, 5), 'title', 'excluded')"

{{services}} — the bare token (no service ID) resolves to the array of all currently selected service IDs. As a sole {{...}} token it returns the array directly (e.g. ["torbox", "realdebrid"]); embedded in a larger string it joins the IDs with commas (e.g. "torbox,realdebrid"). When no services are selected it returns an empty array / empty string.

"enabledServices": "{{services}}"

["torbox"] when TorBox is the only selected service; [] when no services are selected.

{{services.<id>}} resolves to the string "true" or "false" depending on whether the service is selected. As a sole token it returns the boolean directly.

{{services.<id>.<key>}} — Credential refs: Use a two-segment service ref to inject a credential the user entered during service setup into a config field. The placeholder is preserved through template processing and resolved at the final save step, after all inputs are collected.

{
  "type": "newznab",
  "options": {
    "url": "https://search.torbox.app/prowlarr",
    "apiKey": "{{services.torbox.apiKey}}"
  }
}

The <key> must match the credential field name as stored in the service config (e.g. apiKey, token). If the credential has not been entered, the field resolves to an empty string "".

Credential refs are intentionally not evaluated by __if / __switch conditions — they are always preserved as literal strings until the final resolution step. Use services.<id> (without a key) in conditions to check whether a service is selected.

Undefined references resolve to an empty string "".


__if — Conditional Items

Add "__if": "<condition>" to any object inside an array. If the condition is false, the object is removed from the array entirely. If true, the __if key is stripped and the rest of the object is kept.

{
  "excludedStreamExpressions": [
    {
      "__if": "inputs.torboxTier == nonPro",
      "expression": "/*TB Non-Pro Download Limit*/ size(uncached(streams), '200GB')",
      "enabled": true
    },
    {
      "__if": "inputs.optionalFilters includes dvOnlyNonRemux",
      "expression": "/*DV Only Non-Remux*/ negate(...)",
      "enabled": true
    }
  ]
}

[!INFO] __if only works on objects inside arrays; it has no effect at the top level of an object.

Condition Syntax

Form Example Meaning
Bare reference inputs.enableFeature Truthy if value is not false, null, "", or []0 is truthy
Negation !inputs.enableFeature Logical NOT of the bare reference
Equality inputs.tier == premium String value equals "premium"
Inequality inputs.tier != none String value does not equal "none"
Array includes inputs.optionalFilters includes dvPassthrough Array contains the string "dvPassthrough"
Numeric > inputs.count > 5 Numeric value is strictly greater than 5
Numeric >= inputs.count >= 5 Numeric value is greater than or equal to 5
Numeric < inputs.count < 10 Numeric value is strictly less than 10
Numeric <= inputs.count <= 10 Numeric value is less than or equal to 10
Any service (debrid) services True when any service is selected (debrid mode)
No service (P2P) !services True when no service is selected (P2P / no-debrid mode)
Service check services.realdebrid User has realdebrid enabled
Negated service !services.realdebrid User does NOT have realdebrid enabled
Nested subsection inputs.proxy.url Truthy if the url sub-option inside proxy is filled
Compound and inputs.flag and inputs.tier == pro Both sub-conditions must be true (highest precedence)
Compound xor inputs.a xor inputs.b Exactly an odd number of sub-conditions must be true
Compound or services.torbox or services.realdebrid At least one sub-condition must be true (lowest precedence)

Notes:

  • The left-hand side must start with inputs. or services., or be the bare services token.
  • The bare services token (no dot, no ID) is truthy when any service is selected and falsy when none are. Use services for debrid mode and !services for P2P mode.
  • The right-hand side of an operator expression is always treated as a plain string.
  • services and services.* do not support operator forms (==, !=, includes, numeric) — only bare truthiness and compound operators.
  • Negation (!) applies to the entire single condition, including operator results.
  • In compound forms, each sub-expression carries its own ! prefix; the compound itself cannot be negated.
  • Compound operator splitting is boundary-aware: words like "and", "or", "xor" that appear inside a value (e.g. inputs.genre == action and adventure) are not treated as compound separators.

Practical examples:

{ "__if": "inputs.resultLimit" }

→ Included only if the user entered a result limit (non-empty, non-false). Note: a value of 0 is truthy.

{ "__if": "inputs.optionalFilters includes sdrPassthrough" }

→ Included only if the user selected sdrPassthrough in the multi-select.

{ "__if": "inputs.languagePassthrough != none" }

→ Included for any language except "none" (the disabled sentinel value for a select input).

{ "__if": "inputs.tier == pro" }

→ Included only when the user picks the "pro" tier option.


Compound Conditions

Join multiple sub-conditions with and, or, or xor. Each sub-expression is a full single condition (including its own optional !).

Precedence (high → low): and > xor > or

This follows standard logic conventions, so a or b and c means a or (b and c).

{ "__if": "inputs.flag and inputs.tier == pro" }

→ Only when the flag is enabled and tier is "pro".

{ "__if": "services.torbox or services.realdebrid" }

→ When the user has either TorBox or Real-Debrid enabled.

{ "__if": "inputs.flag and !services.torbox" }

→ When flag is set but TorBox is not selected.

{ "__if": "inputs.tier == pro or inputs.tier == premium" }

→ When tier is either "pro" or "premium" (useful when != would be overly permissive).


Numeric Comparisons

Use >, >=, <, <= to compare a number input against a fixed threshold. The left-hand side must be inputs.<key>. String values are coerced to a number; non-numeric values (including undefined) evaluate to false.

{ "__if": "inputs.resultLimit > 0" }

→ Only when the user has entered a positive limit.

{ "__if": "inputs.resultLimit >= 1 and inputs.resultLimit <= 10" }

→ Range check — value must be between 1 and 10 inclusive.

{ "__if": "!inputs.quality < 4" }

→ Negated numeric: true when quality is not less than 4 (i.e. ≥ 4).

For simple "was this filled in" checks on number inputs, the bare form inputs.resultLimit already handles undefined and empty string. Use numeric comparisons when the specific magnitude matters.


At the Object-Key Level

The same __if + __value syntax that filters array items also works at the object-property level. When { "__if": "cond", "__value": X } is the value of a config key, the key is conditionally included or removed:

  • Condition true → the key is set to X (recursed as normal — {{}}, nested __if, etc. all apply)
  • Condition false → the key is absent from the applied config, leaving the user's existing value untouched
{
  "formatter": {
    "__if": "!inputs.retainFormatter",
    "__value": { "id": "tamtaro" }
  },
  "presets": {
    "__if": "inputs.includeAddons",
    "__value": [
      { "type": "torrentio", "enabled": true },
      { "__if": "services.torbox", "type": "torbox-search", "enabled": true }
    ]
  }
}
  • When retainFormatter is falseformatter is set to { "id": "tamtaro" }
  • When retainFormatter is trueformatter is absent from the applied config (user's formatter is kept)
  • When includeAddons is truepresets is set to the addon list (with service-conditional items evaluated inside)
  • When includeAddons is falsepresets is absent (user's addon list is kept)

This is the recommended pattern for "opt-in" config sections — fields the template provides only when the user explicitly asks for them.


__switch — Object Replacement

Replaces an entire object with a different object depending on the value of an input. Used for things like choosing a formatter, swapping a preset's options block, etc.

{
  "__switch": "inputs.<id>",
  "cases": {
    "<value1>": { ...replacement... },
    "<value2>": { ...replacement... }
  },
  "default": { ...fallback... }
}
  • __switch can reference inputs.<id> (nested via inputs.<subsectionId>.<subOptionId> also works), services.<id> (boolean: whether a specific service is selected), or the bare "services" (resolves to the comma-joined string of all selected service IDs — empty string "" when no services are selected).
  • cases keys are matched as strings against the stringified input value.
  • default is used when no case matches. Strongly recommended — omitting it results in null.
  • Replacement objects are recursed into — __if, __switch, and {{}} inside cases all work.

Example — choosing a formatter based on user input:

"formatter": {
  "__switch": "inputs.formatterStyle",
  "cases": {
    "torrentio": { "id": "torrentio" },
    "gdrive": { "id": "gdrive" }
  },
  "default": {
    "id": "prism"
  }
}

When the user picks "torrentio", the formatter becomes { "id": "torrentio" }. Any unmatched selection resolves to the default.

Example — service-conditional preset options:

{
  "presets": [
    {
      "__if": "inputs.includeDebridio",
      "type": "debridio",
      "instanceId": "abc",
      "enabled": true,
      "options": {
        "name": "Debridio"
      }
    }
  ]
}

Example — debrid vs P2P branching with bare services:

Use __switch: "services" with "" as the P2P case key. The resolved value is the comma-joined service ID string, so "" reliably matches the no-service (P2P) state:

"preferredStreamTypes": {
  "__switch": "services",
  "cases": {
    "": ["p2p"]
  },
  "default": ["cached", "usenet"]
}

When no service is selected, the user is in P2P mode and only p2p stream types are preferred. When any service is selected (debrid mode), cached and usenet are preferred.

This pattern extends naturally to nested switches — the outer __switch: "services" splits debrid vs P2P, and each branch can then independently __switch on a user input:

"sortCriteria": {
  "__switch": "services",
  "cases": {
    "": {
      "__switch": "inputs.sortPref",
      "cases": {
        "speed": { "global": [{ "by": "seeders", "direction": "asc" }] }
      },
      "default": { "global": [{ "by": "seeders", "direction": "desc" }] }
    }
  },
  "default": {
    "__switch": "inputs.sortPref",
    "cases": {
      "quality": { "global": [{ "by": "resolution", "direction": "desc" }] },
      "speed":   { "global": [{ "by": "size",       "direction": "asc"  }] }
    },
    "default": { "global": [{ "by": "size", "direction": "desc" }] }
  }
}

Note on cases keys: In __switch: "services", the resolved value is the comma-joined service ID string (e.g. "torbox", "torbox,realdebrid"). The only reliably matchable key is "" for the no-service (P2P) case. Use __if: "services" or __if: "!services" on individual array items if you need per-item debrid/P2P gating instead.

__value — Conditional Array Values

Add "__value": <string | string[]> to an object inside an array to inject values directly into the parent array rather than inserting the object itself. Combine with "__if" for conditional injection.

  • String value → inserts one item
  • Array value → spreads all items into the parent array
  • {{inputs.<id>}} interpolation works in both forms
{
  "excludedVisualTags": [
    "3D",
    { "__if": "inputs.excludeDV", "__value": "DV" },
    { "__if": "inputs.excludeHdr", "__value": ["HDR", "HDR10", "HDR10+"] }
  ]
}

If excludeDV is true and excludeHdr is false, this resolves to ["3D", "DV"]. If both are true, it resolves to ["3D", "DV", "HDR", "HDR10", "HDR10+"].

__value without __if is always injected:

{
  "excludedVisualTags": [
    { "__value": ["H-OU", "H-SBS"] },
    { "__if": "inputs.excludeAi", "__value": "AI" }
  ]
}

This is the recommended pattern for user-controlled membership in flat string[] fields (e.g. excludedVisualTags, preferredResolutions). It scales to any number of independent boolean inputs without the combinatorial explosion of __switch.

__value is only meaningful inside arrays. Using it outside an array context has no defined behaviour.


__remove — Drop a Config Key

Place { "__remove": true } as the value of any config key to unconditionally remove that key from the applied config. The key will not be present in the merged result, leaving the user's existing value untouched.

{
  "formatter": { "__remove": true }
}

This is most useful as a __switch case value to make "don't touch this field" a selectable outcome:

"formatter": {
  "__switch": "inputs.formatterChoice",
  "cases": {
    "retain": { "__remove": true },
    "tamtaro": { "id": "tamtaro" },
    "prism":   { "id": "prism" }
  },
  "default": { "id": "tamtaro" }
}

When the user picks "retain", the formatter key is absent from the applied config — their current formatter is left unchanged. Any other choice sets the formatter to the specified value.

For the common two-option case ("use template's value" vs. "keep mine"), __if + __value is simpler:

{ "__if": "!inputs.retainFormatter", "__value": { "id": "tamtaro" } }

Use __remove inside __switch when you need three or more selectable outcomes, one of which is "leave unchanged".


Template Placeholders

For fields the user should fill in themselves after loading (without an inputs dialog), use these sentinel strings:

Placeholder string Meaning
"<template_placeholder>" Required — the field must be filled in
"<required_template_placeholder>" Same as above, explicit alias
"<optional_template_placeholder>" Optional — the field can be left blank
{
  "tmdbApiKey": "<template_placeholder>",
  "rpdbApiKey": "<optional_template_placeholder>"
}

The frontend highlights these values as unfilled after the template is applied.


Validation Rules

When a template is imported or viewed, validation runs automatically and flags issues.

Errors (block loading)

  • metadata.name, description, author, category must be non-empty strings.
  • metadata.version must be a valid semver string (1.0.0).
  • metadata.source must be "builtin", "custom", or "external".
  • __if condition must be a non-empty string.
  • __if namespace must be inputs or services.
  • __switch reference must start with inputs. or services..

Warnings (allowed but flagged)

  • {{inputs.<id>}} token references an ID not declared in metadata.inputs.
  • __if references an inputs.<id> not in metadata.inputs.
  • __switch references an input ID not in metadata.inputs.
  • __switch is missing a cases object (always resolves to default).

Quick-Reference: Expression Summary

Condition (in __if):
  inputs.<id>                     → truthy check (0 is truthy; undefined/null/''/false/[] are falsy)
  !inputs.<id>                    → falsy check
  inputs.<id> == value            → equality
  inputs.<id> != value            → inequality
  inputs.<id> includes value      → array contains
  inputs.<id> > n                 → numeric greater-than
  inputs.<id> >= n                → numeric greater-than-or-equal
  inputs.<id> < n                 → numeric less-than
  inputs.<id> <= n                → numeric less-than-or-equal
  services                        → any service selected (debrid mode)
  !services                       → no service selected (P2P / no-debrid mode)
  services.<serviceId>            → specific service enabled
  !services.<serviceId>           → specific service not enabled
  inputs.<sub>.<subOpt>           → nested subsection value

Compound conditions (precedence: and > xor > or):
  <expr> and <expr>               → both must be true
  <expr> or  <expr>               → at least one must be true
  <expr> xor <expr>               → odd number must be true
  !inputs.a and inputs.b          → each sub-expression carries its own !

Object replacement (in any object position):
  { "__switch": "inputs.<id>", "cases": { ... }, "default": { ... } }
  { "__switch": "services", "cases": { "": <p2p value> }, "default": <debrid value> }
                                  → "" case matches when no services are selected (P2P mode)

Conditional array value injection (inside arrays only):
  { "__value": "string" }                   → always inject one item
  { "__value": ["a", "b"] }                 → always spread items
  { "__if": "...", "__value": "string" }    → inject one item if condition passes
  { "__if": "...", "__value": ["a", "b"] }  → spread items if condition passes

Conditional object key (object property level):
  { "key": { "__if": "cond", "__value": X } }
    → condition true:  key is set to X (recursed)
    → condition false: key is absent from applied config

Unconditional key removal:
  { "key": { "__remove": true } }           → key always absent from applied config
  (also valid as a __switch case value)

String interpolation (in any string value):
  "{{services}}"                      → array of selected service IDs (sole token)
                                        or comma-joined string (multi-token)
  "{{inputs.<id>}}"                   → raw value (type-preserving if sole token)
  "{{services.<id>}}"                 → true/false (boolean if sole token, string otherwise)
  "{{services.<id>.<key>}}"           → credential ref: resolved to the user's entered
                                        credential value at final save; empty string if missing
  "prefix_{{inputs.<id>}}_suffix"     → concatenated string

Sharing Templates

Adding to an instance.

Instance hosters can add their own templates via either the templates folder in the data directory or via the TEMPLATE_URLS environment variable.

The data directory is ./data by default but will change if you have your DATABASE_URI set to a different folder and are using SQLite. In the docker container, this will be at /app/data/templates. Templates provided here will show above the "built-in" templates.

TEMPLATE_URLS is a JSON format array of URLs that point to template JSON files. These are fetched at startup and refreshed periodically.

Users can then choose from these templates within the AIOStreams interface. Additionally, users can import templates via the template browser from:

  • A URL pointing to a template file or an array of template objects.
  • A local template file on their device.

Deep Link

You can send a user directly to the template import flow by appending query parameters to any AIOStreams URL. When the page loads, the template modal opens automatically and begins importing from the provided URL.

Parameters

Parameter Required Description
template Yes URL pointing to a template JSON file (single object or array of objects).
templateId No The metadata.id of a specific template to auto-select from the fetched file.

Example

https://your-aiostreams-instance.com/stremio/configure?template=https://example.com/my-template.json

With a specific template pre-selected from a multi-template file:

https://your-aiostreams-instance.com/stremio/configure?template=https://example.com/templates.json&templateId=author.my-template

Security note: Users are shown a warning when a template is imported via deep link. Remind users in your documentation to only import templates from sources they trust — deep links can come from anywhere.


Clone this wiki locally