AIOStreamsAIOStreams
Reference

Custom Formatter

Complete reference for formatting stream names and descriptions using variables, modifiers, and conditionals.

The Custom Formatter gives you full control over how each stream's name and description are displayed in Stremio (and other clients). You can live-preview your format on the configuration page using the Preview button.

Access a variable using:

{variableName.propertyName}

Modifiers are chained with :::

{variableName.propertyName::modifier}

Variables

Config

VariableTypeDescription
{config.addonName}stringThe name of the AIOStreams instance (Branding → Addon name; ADDON_NAME)

Stream

Source

VariableTypeDescription
{stream.type}stringType: debrid, usenet, http, live, youtube, p2p
{stream.proxied}booleanWhether the stream is proxied (e.g. MediaFlow)
{stream.library}booleanWhether the file is already in your "library" e.g. debrid account
{stream.indexer}stringSource indexer
{stream.message}stringAdditional status message
{stream.infoHash}stringTorrent info hash

File

VariableTypeDescription
{stream.filename}stringFilename of the stream or media file
{stream.folderName}stringFolder name (usually only specific addons)
{stream.size}numberFile size in bytes
{stream.folderSize}numberFolder/torrent size in bytes
{stream.bitrate}numberBitrate in bits per second
{stream.duration}numberMedia duration in seconds
{stream.container}stringFile container format (e.g. mkv, mp4)
{stream.extension}stringFile extension (e.g. .mkv, .iso)

Video

VariableTypeDescription
{stream.quality}stringQuality tag (e.g. Bluray, WEB-DL)
{stream.resolution}stringVideo resolution (e.g. 1080p, 2160p)
{stream.visualTags}string[]Visual tags (e.g. HDR, DV, HDR10+)
{stream.encode}stringEncoding format (e.g. HEVC, AVC, AV1)
{stream.network}stringSource network (e.g. Netflix, Disney+)
{stream.hasChapters}booleanWhether the file contains chapters

Audio

VariableTypeDescription
{stream.audioTags}string[]Audio tags (e.g. Atmos, DTS-HD MA, DD+)
{stream.audioChannels}string[]Audio channels (e.g. 5.1, 7.1)

Languages

VariableTypeDescription
{stream.languages}string[]Languages extracted from filename or audio track info
{stream.languageEmojis}string[]languages as emoji flags
{stream.languageCodes}string[]languages as ISO 639 codes
{stream.smallLanguageCodes}string[]languages as small-caps codes
{stream.uLanguages}string[]languages filtered to those you have configured (preferred, required, or included but not excluded)
{stream.uLanguageEmojis}string[]uLanguages as emoji flags
{stream.uLanguageCodes}string[]uLanguages as ISO 639 codes
{stream.uSmallLanguageCodes}string[]uLanguages as small-caps codes
{stream.dubbed}booleanWhether the release is dubbed

Subtitles

VariableTypeDescription
{stream.subtitles}string[]Embedded subtitle languages (when known — see note below)
{stream.subtitleEmojis}string[]subtitles as emoji flags
{stream.subtitleCodes}string[]subtitles as ISO 639 codes
{stream.smallSubtitleCodes}string[]subtitles as small-caps codes
{stream.uSubtitles}string[]subtitles filtered to those you have configured (preferred, required, or included but not excluded)
{stream.uSubtitleEmojis}string[]uSubtitles as emoji flags
{stream.uSubtitleCodes}string[]uSubtitles as ISO 639 codes
{stream.uSmallSubtitleCodes}string[]uSubtitles as small-caps codes
{stream.subbed}booleanWhether the release has subtitles

languages vs subtitles

languages and subtitles behave differently depending on what information is available for a stream.

When accurate media info is available, languages contains audio track languages and subtitles contains embedded subtitle languages. This is the case for:

  • Debrid results from built-in and service-wrapped addons that have media_info from StremThru (crowdsourced/probed via FFmpeg)
  • Torznab/Newznab results from indexers that provide separate audio/subtitle metadata
  • nekoBT results (all results have accurate audio/subtitle info)
  • Torrentio anime results (subtitles field may be populated)

In all other cases, subtitles is empty and languages contains every language found in the filename — including subtitle-only languages (e.g. a filename containing Eng.Sub will add English to languages, not subtitles). This is a known limitation of filename-only parsing.

Release

VariableTypeDescription
{stream.title}stringMedia title extracted from filename
{stream.year}stringYear extracted from filename
{stream.date}stringDate extracted from filename
{stream.releaseGroup}stringName of the release group
{stream.editions}string[]Special editions (e.g. Director's Cut)
{stream.repack}booleanWhether the release is a repack
{stream.regraded}booleanWhether the content is regraded
{stream.uncensored}booleanWhether the content is uncensored
{stream.unrated}booleanWhether the content is unrated
{stream.upscaled}booleanWhether the content has been upscaled

Season / Episode

VariableTypeDescription
{stream.seasonPack}booleantrue if part of a season pack
{stream.seasons}number[]Detected season numbers
{stream.formattedSeasons}stringFormatted season string (e.g. S01 or S01-05)
{stream.folderSeasons}number[]Seasons from folder name (when different from filename)
{stream.formattedFolderSeasons}stringFormatted seasons from folder name
{stream.episodes}number[]Detected episode numbers
{stream.formattedEpisodes}stringFormatted episode string (e.g. E01 or E01-05)
{stream.folderEpisodes}number[]Episodes from folder name (when different from filename)
{stream.formattedFolderEpisodes}stringFormatted episodes from folder name
{stream.seasonEpisode}string[]Pre-formatted season/episode strings (e.g. ['S01', 'E05'])

P2P / Tracker

VariableTypeDescription
{stream.seeders}numberTorrent seeder count
{stream.private}booleantrue if from a private tracker
{stream.freeleech}booleantrue if the torrent is freeleech
{stream.age}stringHuman-readable age since release
{stream.ageHours}numberAge in hours

Anime

VariableTypeDescription
{stream.seadex}booleanWhether listed as best/alt on SeaDex
{stream.seadexBest}booleanWhether listed as a best release on SeaDex

Scoring

VariableTypeDescription
{stream.regexMatched}stringName of the highest-priority matched preferred regex
{stream.rankedRegexMatched}string[]All matched Ranked Regex Filter names (sorted)
{stream.regexScore}numberScore from matched Regex Filter
{stream.nRegexScore}numberRegex score normalised to 0–100
{stream.seScore}numberScore from matched Stream Expression sort rule
{stream.nSeScore}numberStream Expression score normalised to 0–100
{stream.seMatched}stringName of the Preferred Stream Expression that matched (from first comment)
{stream.rseMatched}string[]Names of all Ranked Stream Expressions that matched

Service

VariableTypeDescription
{service.id}stringService identifier (e.g. realdebrid)
{service.shortName}stringAbbreviated name (e.g. RD)
{service.name}stringFull name (e.g. Real-Debrid)
{service.cached}booleanWhether the stream is cached

Addon

VariableTypeDescription
{addon.presetId}stringThe preset ID the addon was generated from
{addon.name}stringDisplay name of the addon
{addon.manifestUrl}stringThe addon's manifest URL

Metadata

VariableTypeDescription
{metadata.queryType}stringMedia type being queried (movie, series, anime.series, or anime.movie)
{metadata.title}stringTitle of the media being queried
{metadata.runtime}numberMovie runtime in minutes
{metadata.episodeRuntime}numberEpisode runtime in minutes
{metadata.genres}string[]List of genres
{metadata.year}numberRelease year

Debug

VariableTypeDescription
{debug.json}stringRaw JSON of the stream data
{debug.jsonf}stringPretty-printed JSON of the stream data

Modifiers

Modifiers are applied left to right, and each one acts on the result of the last:

{stream.filename::lower::truncate(20)}

Field and modifier names are case-insensitive, so {stream.filename} and {Stream.FileName} are the same. Whitespace inside the braces is ignored.

Applying a modifier to a field that has no value renders nothing, so {stream.size::bytes} is empty rather than an error when the size is unknown. Applying one to the wrong type is an error and is shown inline, e.g. {stream.filename::bytes} renders {unknown_string_modifier(bytes)}.

Any Type

ModifierDescription
::default('text')Use text when the value is missing
::stringConvert to string
{stream.quality::default('Unknown')}   ->  BluRay, or Unknown when absent
{stream.library::string}               ->  true

A quoted value can be used wherever a field is expected, which is useful for piping fixed text through modifiers:

{'n/a'::upper}          ->  N/A
{'ready'::smallcaps}    ->  ʀᴇᴀᴅʏ

String Modifiers

ModifierDescription
::upperConvert to UPPERCASE
::lowerConvert to lowercase
::smallcapsConvert letters to ꜱᴍᴀʟʟ ᴄᴀᴘꜱ
::subscriptConvert digits to ₀₁₂₃₄₅₆₇₈₉
::superscriptConvert digits to ⁰¹²³⁴⁵⁶⁷⁸⁹
::translate('from', 'to')Map characters by position
::titleTitle Case (capitalise first letter of each word)
::replace('find', 'replaceWith')Replace all occurrences of find
::remove('a', 'b', …)Delete every occurrence of each argument
::truncate(N)Truncate to N characters and append
::date('pattern')Reformat a date — see Date patterns
::lengthReturn length of the string
::reverseReverse the string
::base64Encode as base64

::smallcaps only affects letters, so it combines with the digit modifiers:

{stream.resolution::smallcaps}              ->  2160ᴘ
{stream.resolution::subscript::smallcaps}   ->  ₂₁₆₀ᴘ

::translate maps each character of from to the character at the same position in to. Characters without a counterpart are left unchanged, so a shorter to never drops text:

{stream.quality::translate('Bl','Яⅼ')}   ->  ЯⅼuRay

Number Modifiers

ModifierDescription
::bytes / ::bytes10Format as base-10 bytes (KB, MB, GB)
::sbytes / ::sbytes10Concise base-10 byte format
::bytes2Format as base-2 bytes (KiB, MiB, GiB)
::sbytes2Concise base-2 byte format
::rbytes / ::rbytes10Like ::bytes but rounded
::rbytes2Like ::bytes2 but rounded
::bitrateFormat as bitrate (Kbps, Mbps)
::rbitrateLike ::bitrate but rounded
::sbitrateConcise bitrate (e.g. 5.2 Mbps)
::timeFormat milliseconds as 1h:23m:45s
::time('pattern')Format milliseconds — see Duration patterns
::starStar rating ★ out of 5 from a 0–100 score
::pstarPadded star rating (always 5 stars e.g. ★★★☆☆)
::hexEncode to hexadecimal
::octalEncode to octal
::binaryEncode to binary

Date & Duration Patterns

::date('…') and ::time('…') take a pattern string, so you can build any style you like instead of picking from a fixed list.

Both share the same rules:

  • %X is a token, replaced with a value. Everything else is literal text.
  • %-X is the unpadded form of %X (7 instead of 07).
  • [...] marks an optional group: it disappears when every token inside it is zero. Use it to hide empty units.
  • %%, %[ and %] emit a literal %, [ and ].
  • An unrecognised token is left as-is, so typos show up in the output.

Duration patterns

Used by ::time('…') on {stream.duration}.

TokenDescriptionExample
%HHours, zero padded01
%-HHours1
%MMinutes, zero padded23
%-MMinutes23
%SSeconds, zero padded45
%-SSeconds45

The largest unit in the pattern carries the overflow, so a pattern with no %H reads minutes as the total:

Pattern1h 23m 45s45s
'%H:%M:%S'01:23:4500:00:45
'%-Hh %-Mm'1h 23m0h 0m
'[%-Hh ]%-Mm'1h 23m0m
'[%-Hh ]%-Mm[ %-Ss]'1h 23m 45s0m 45s
'%-M min'83 min0 min

Date patterns

Used by ::date('…') on {stream.date}. Dates are read in UTC; anything that isn't a valid YYYY-MM-DD is passed through untouched.

TokenDescriptionExample
%YFull year2023
%yTwo-digit year23
%mMonth number, zero padded07
%-mMonth number7
%BMonth nameJuly
%bShort month nameJul
%dDay of month, zero padded04
%-dDay of month4
%oDay of month with ordinal suffix4th
%AWeekday nameTuesday
%aShort weekday nameTue
Pattern2023-07-04
'%Y-%m-%d'2023-07-04
'%-d %b %Y'4 Jul 2023
'%B %o, %Y'July 4th, 2023
'%d/%m/%y'04/07/23
'%b %Y'Jul 2023
'%A'Tuesday

Patterns chain with the other modifiers as usual:

{stream.date::date('%B %o, %Y')::upper}   ->  JULY 4TH, 2023

%o already includes the day number, so write %B %o (July 4th) — not %B %-d%o, which would render July 44th.

Array Modifiers

ModifierDescription
::join('separator')Join elements with separator
::slice(start, end)Return a section (end is optional)
::lengthNumber of elements
::firstFirst element
::lastLast element
::randomSingle random element
::sortSort (alphabetical for strings, numerical for numbers)
::rsortReverse sort
::lsortLexicographic sort (case-sensitive)
::reverseReverse order

Conditional Modifiers

Conditionals evaluate to true or false and control what text is shown:

{variable.property::conditionalModifier["trueString"||"falseString"]}

Example: Show seeders only if greater than 1:

{stream.seeders::>1["Seeders: {stream.seeders}"||""]}
ModifierDescriptionTypes
istrueValue is trueboolean
isfalseValue is falseboolean
existsNot null, undefined, empty string, or empty arraystring, array, any
in('a', 'b', …)Value is one of the listed optionsstring, number, boolean, string[]
$XStarts with X / first array element is Xstring, string[]
^XEnds with X / last array element is Xstring, string[]
~XContains Xstring, string[]
=XExactly equal to Xstring, number
>=XGreater than or equal to Xnumber
<=XLess than or equal to Xnumber
>XGreater than Xnumber
<XLess than Xnumber

in saves repeating a field across several = comparisons, and matches if any element of an array is listed:

{service.id::in('torbox','realdebrid')["🟩"||"⬜"]}
{stream.languages::in('english')["🇬🇧"||""]}

A boolean field needs no modifier at all, and accepts an optional third branch for when the value is missing rather than false:

{service.cached["Cached"||"Uncached"||"Unknown"]}

  cached      ->  Cached
  not cached  ->  Uncached
  unknown     ->  Unknown

Without a third branch, a missing value renders nothing. istrue and isfalse both evaluate to false for a missing value, so use the third branch when the three states need distinguishing.

Nesting

A conditional can contain another conditional inside either branch. Quotes belonging to the inner one must be escaped with \":

{stream.resolution::exists["{stream.quality::exists[\"{stream.resolution} {stream.quality}\"||\"{stream.resolution}\"]}"||"Unknown"]}

  2160p + BluRay  ->  2160p BluRay
  2160p only      ->  2160p
  neither         ->  Unknown

Nesting is limited to five levels deep. Beyond that the branch is emitted as plain text.


Optional Groups

{? ... ?} renders only when every field inside it has a value. If any is missing, the whole group disappears, including its literal text.

{?📅 {stream.age} ?}

  age known    ->  📅 30d
  age unknown  ->  (nothing, not even the icon)

Groups nest, so a separator can depend on its own field:

{?[{stream.resolution}{? · {stream.quality}?}]?}

  2160p + BluRay  ->  [2160p · BluRay]
  2160p only      ->  [2160p]
  neither         ->  (nothing)

A group tests whether the field has a value, not whether the rendered text looks empty. {?{stream.type::replace('debrid','')}?} still renders, because stream.type was present — the modifier just blanked it. This matches ::exists.

Conditionals

Chain multiple conditions together using and, or, or xor:

{var1::cond1::or::var2::cond2["trueString"||"falseString"]}

Conditions are evaluated left to right: (x and y or z)((x and y) or z).

OperatorDescription
andBoth expressions must be true
orAt least one expression must be true
xorExactly one expression must be true

Example: Show seeders only when the stream is either uncached or a P2P torrent:

{service.cached::isfalse::or::stream.type::=p2p::and::stream.seeders::>0["Seeders: {stream.seeders}"||""]}

Tools

Use formatting tools with {tools.toolName}:

ToolDescription
{tools.newLine}Add a newline at this position
{tools.removeLine}Remove the entire line wherever found

{tools.newLine} works in the formatter preview but how it renders in Stremio is platform-dependent. Many platforms do not respect newlines in the name field. Avoid building your name template around {tools.newLine}.


Chillio

For Chillio, the name template maps to the ChillLink title field. The description template is split line-by-line into metadata strings — each line becomes a separate metadata entry.


Examples

Community-created formats are shared on the Discord Server.

You can also view the built-in formatter definitions at: packages/core/src/formatters/predefined.ts

On this page