Version: 1.0
Date: February 11, 2026
Status: Implemented Schema
Runtime Support: This schema is enforced by the TizenPortal bundle manifest validator; the current supported schema version is 1.0.
This document defines the complete schema for bundle manifest.json files. The manifest serves as the single source of truth for bundle metadata, configuration options, and runtime behavior.
{
"name": "string (required)",
"displayName": "string (required)",
"version": "string (required)",
"author": "string (optional)",
"description": "string (required)",
"homepage": "string (optional)",
"requires": ["string array (optional)"],
"provides": ["string array (optional)"],
"navigationMode": "string | object (optional)",
"viewportLock": "string | boolean (optional)",
"options": ["array of option objects (optional)"],
"features": "object (optional)"
}
name (string, required)"audiobookshelf", "adblock"displayName (string, required)"Audiobookshelf", "Ad Blocker"version (string, required)"1.0.0", "2.1.3"author (string, optional)"TizenPortal", "Community"description (string, required)"Media controls, library navigation, and player integration for Audiobookshelf"homepage (string, optional)"https://www.audiobookshelf.org/"requires (array of strings, optional)["focus-styling", "tabindex-injection"]provides (array of strings, optional)["focus-styling", "focusable-elements", "media-controls"]navigationMode (string | object, optional)"directional", "geometric", or "polyfill"{ "mode": "string", "required": boolean }"navigationMode": "directional"
"navigationMode": {
"mode": "directional",
"required": false
}
Mode Descriptions:
"directional" - Cone-based navigation (30° arc), forgiving, PREFERRED for most sites"geometric" - Strict axis-aligned navigation, best for perfect grids"polyfill" - Legacy spatial-navigation-polyfill.js, backwards compatibility onlyviewportLock (string | boolean, optional)true = lock (default), false = unlock"force" = force lock (prevent user override)true (locked)"viewportLock": true
"viewportLock": "force"
Values:
true - Lock viewport to 1920px (user can override in card settings)false - Don’t lock viewport (allow responsive behavior)"force" - Force viewport lock (prevent user override)options (array of objects, optional)Option Object Schema:
{
"key": "string (required)",
"label": "string (required)",
"type": "string (required)",
"default": "any (optional)",
"placeholder": "string (optional)",
"description": "string (optional)",
"validation": "object (optional)"
}
Option Types:
"toggle" - Boolean on/off switch"text" - Text input field"url" - URL input with validation"number" - Numeric input"select" - Dropdown selection"color" - Color picker"textarea" - Multi-line textExample:
"options": [
{
"key": "strict",
"label": "Strict Mode",
"type": "toggle",
"default": false,
"description": "Enable stricter ad blocking rules"
},
{
"key": "allowlistUrl",
"label": "Allowlist URL",
"type": "url",
"placeholder": "https://example.com/allowlist.txt",
"description": "URL to custom allowlist file"
},
{
"key": "hideCookieBanners",
"label": "Hide Cookie Banners",
"type": "toggle",
"default": false,
"description": "Automatically hide cookie consent banners"
}
]
Access in Bundle:
onActivate(window, card) {
const options = card.bundleOptions || {};
const strictMode = options.strict !== undefined ? options.strict : false;
const allowlistUrl = card.bundleOptionData?.allowlistUrl || '';
if (strictMode) {
// Apply strict rules
}
if (allowlistUrl) {
// Load allowlist from URL
}
}
features (object, optional)Available Features:
"features": {
"focusStyling": true,
"focusOutlineMode": "on",
"focusTransitions": true,
"focusTransitionMode": "slide",
"focusTransitionSpeed": "medium",
"tabindexInjection": true,
"scrollIntoView": true,
"safeArea": false,
"gpuHints": true,
"cssReset": true,
"hideScrollbars": false,
"wrapTextInputs": true,
"uaMode": "tizen"
}
Example:
"features": {
"tabindexInjection": false,
"scrollIntoView": false,
"hideScrollbars": true
}
Priority: Card override > Bundle default > Global preference
{
"name": "my-bundle",
"displayName": "My Bundle",
"version": "1.0.0",
"description": "Basic bundle with focus styling and navigation"
}
{
"name": "adblock",
"displayName": "Ad Blocker",
"version": "1.0.0",
"author": "TizenPortal",
"description": "Blocks advertisements and tracking scripts for cleaner browsing",
"provides": ["ad-blocking", "tracker-blocking", "cookie-banner-hiding"],
"navigationMode": "geometric",
"viewportLock": false,
"options": [
{
"key": "strict",
"label": "Strict Mode",
"type": "toggle",
"default": false,
"description": "Enable stricter ad blocking rules"
},
{
"key": "allowlistUrl",
"label": "Allowlist URL",
"type": "url",
"placeholder": "https://example.com/allowlist.txt",
"description": "URL to custom allowlist file"
},
{
"key": "hideCookieBanners",
"label": "Hide Cookie Banners",
"type": "toggle",
"default": false,
"description": "Automatically hide cookie consent banners"
},
{
"key": "inlineHeuristics",
"label": "Inline Ad Heuristics",
"type": "toggle",
"default": true,
"description": "Use heuristics to detect inline ad scripts"
}
],
"features": {
"cssReset": false,
"hideScrollbars": false
}
}
{
"name": "audiobookshelf",
"displayName": "Audiobookshelf",
"version": "1.0.0",
"author": "TizenPortal",
"description": "Media controls, library navigation, and player integration for Audiobookshelf",
"homepage": "https://www.audiobookshelf.org/",
"requires": ["focus-styling", "media-keys"],
"provides": ["media-controls", "library-navigation", "player-integration"],
"navigationMode": {
"mode": "directional",
"required": false
},
"viewportLock": "force",
"features": {
"tabindexInjection": true,
"scrollIntoView": true,
"wrapTextInputs": true
}
}
The manifest should be loaded during the Rollup build and merged with the bundle object:
// rollup.config.js
function generateBundleRegistry() {
// ... existing code ...
for (var j = 0; j < bundleDirs.length; j++) {
var name = bundleDirs[j];
var manifestPath = path.join(bundlesDir, name, 'manifest.json');
if (existsSync(manifestPath)) {
var manifestContent = readFileSync(manifestPath, 'utf-8');
// Manifest will be imported and merged at runtime
}
}
}
// In bundle main.js lifecycle hooks
export default {
// manifest will be attached automatically at runtime
onActivate(window, card) {
// Access via this.manifest
const manifest = this.manifest;
console.log('Bundle:', manifest.displayName);
console.log('Version:', manifest.version);
// Or access via bundle reference
const bundle = TizenPortal.loader.getActiveBundle();
console.log('Navigation mode:', bundle.manifest.navigationMode);
}
}
Runtime should validate manifests on load:
Status: ✅ All phases complete!
displayName, description, author from main.js to manifest.jsonnavigationMode, viewportLock, options from main.js to manifest.jsonfeatures overrides with priority systemrequires/provides dependency systemmanifest.json is now the single source of truth for all bundle configuration.