How to Contribute to Invidious Frontend Development: A Complete Guide

To contribute to Invidious frontend development, modify the server-rendered ECR templates in src/invidious/views/ for structural changes, edit the client-side JavaScript in assets/js/ for interactive features, and rebuild static assets using make assets before submitting your pull request to the iv-org/invidious repository.

Invidious uses a hybrid architecture combining Crystal-based server-side rendering with client-side JavaScript to deliver a lightweight YouTube frontend. Understanding how data flows from Crystal route handlers through ECR templates into JSON blobs consumed by Video.js-powered scripts is essential for making effective contributions.

Understanding the Frontend Architecture

The Invidious frontend consists of two distinct layers that communicate through embedded JSON data structures.

Server-Side Rendering with ECR Templates

Each page is assembled from ECR (Embedded Crystal) templates that receive strongly typed Crystal models such as VideoData and PlayerData. These views inject JSON blobs directly into the DOM using script tags like <script id="player_data">…</script>, which client-side code then consumes. Key templates include src/invidious/views/watch.ecr for the video page and component partials like src/invidious/views/components/player.ecr and src/invidious/views/components/subscribe_widget.ecr.

Client-Side JavaScript Stack

The assets/js/ directory contains the bundled JavaScript that powers interactivity. The player.js file initializes Video.js, attaches hotkey listeners, handles caption loading, and manages the control bar through the controlBar.children configuration array. The watch.js file acts as UI glue, managing playlist navigation, comment source switching via swap_comments, and continue-autoplay logic through the next_video function. Helper utilities reside in assets/js/_helpers.js, providing helpers.xhr for API calls and helpers.storage for local state.

Data Flow from Routes to Renderer

When a user requests a video, the flow follows this pattern:

  1. Route handlersrc/invidious/routes/watch.cr contains Invidious::Routes::Watch.handle, which parses query parameters, fetches metadata, and constructs the video_data and player_data structs (lines 86–92 for asset construction).
  2. Template renderingwatch.ecr renders the HTML and embeds the data as JSON text content inside script tags.
  3. Client initializationassets/js/player.js parses these blobs using JSON.parse(document.getElementById('player_data').textContent) and initializes the Video.js instance with quality selectors and hotkey bindings.
  4. State persistence – User interactions trigger updates to the PREFS cookie via the updateCookie helper (lines 1088–1115 in player.js).

Setting Up Your Development Environment

Before making frontend contributions, prepare a local instance with Crystal and Node tooling.

  • Install Crystal version 1.12 or higher.
  • Clone the repository and run make dev to start the development server on http://localhost:3000.
  • Run make assets to compile and minify JavaScript and CSS into the final static bundle.
  • Execute make test to run Crystal unit tests and the JavaScript linter via npm run lint.

Common Frontend Contribution Patterns

Most frontend tasks involve extending existing patterns in either the ECR templates or the JavaScript modules.

Adding Player Controls

To add a button to the Video.js control bar, modify assets/js/player.js by extending the options.controlBar.children array (defined around lines 12–28) and registering a custom component. For example, adding a loop toggle requires inserting the component name before the fullscreenToggle entry and defining the click handler that calls player().loop().

Modifying Comment Sections

Comment source switching lives in assets/js/watch.js within the swap_comments function (lines 14–22). To add a new comment provider, extend the conditional logic to handle your new source type, then update the ECR template at src/invidious/views/components/comments.ecr to include the new toggle link with the appropriate data-comments attribute.

Persisting User Preferences

Runtime preferences such as volume and playback speed use the updateCookie helper in assets/js/player.js. When adding a new preference like dark mode, toggle the class on the document body, then write to a dedicated cookie using the same formatting pattern used for PREFS in lines 1088–1115.

Step-by-Step Contribution Workflow

Follow this sequence when implementing frontend features:

  1. Identify the target layer – Determine if your change requires modifying a Crystal route (src/invidious/routes/*.cr), an ECR view (src/invidious/views/*.ecr), or client-side scripts (assets/js/*.js).
  2. Follow existing patterns – For JavaScript, use the module style in player.js: declare helper functions, use helpers.xhr for API calls, and attach listeners via player.on. For ECR, use Crystal interpolation ({{ }}) and avoid inline scripts; place data in JSON blobs instead.
  3. Implement and rebuild – After editing JavaScript or CSS, run make assets to regenerate the static bundle.
  4. Validate locally – Open the affected page in your browser, check the console for errors, and verify that hotkeys (handled in lines 61–78 of player.js), playback rate controls, and caption toggles remain functional.
  5. Run quality checks – Execute make test to ensure Crystal tests pass and JavaScript linting succeeds.
  6. Submit your changes – Fork the repository, create a feature branch, commit with clear messages, and open a pull request following the guidelines in the project README.

Key Source Files for Frontend Developers

When contributing to Invidious frontend development, these files contain the core logic:

  • src/invidious/routes/watch.cr – Handles HTTP requests for video pages, constructs VideoAssets structs, and performs permission checks before rendering.
  • src/invidious/views/watch.ecr – Main template for video pages that injects JSON data and includes component partials.
  • src/invidious/views/components/player.ecr – Renders the HTML5 video element markup consumed by Video.js.
  • assets/js/player.js – Core client logic including Video.js initialization, hotkey handling (lines 61–78), and cookie management.
  • assets/js/watch.js – Playlist navigation, autoplay logic (lines 30–43 for next_video), and comment source switching.
  • assets/js/_helpers.js – Utility functions for XHR requests and storage management used across the frontend.
  • Makefile – Defines dev, assets, and test targets for building and validation.

Summary

  • Invidious frontend development requires understanding both Crystal/ECR server-side rendering and Video.js client-side initialization.
  • Modify src/invidious/routes/watch.cr to change data structures and assets/js/player.js to adjust player behavior.
  • Use make assets to rebuild static files and make test to verify changes before submitting.
  • Follow the established data flow: route handlers populate structs, ECR templates embed JSON, and JavaScript parses the data to initialize UI components.
  • Persist user preferences using the updateCookie helper and the PREFS cookie pattern.

Frequently Asked Questions

What programming languages do I need to know to contribute to Invidious frontend development?

You need Crystal for server-side route handlers and ECR templates, plus JavaScript for client-side interactivity. Understanding Video.js APIs is essential for player modifications, while basic SQL knowledge helps if your changes involve the database layer that feeds the frontend models.

How do I test my frontend changes locally?

Run make dev to start a local server on port 3000 after installing Crystal 1.12 or higher. Modify your target files, then run make assets to compile JavaScript and CSS. Refresh your browser to see changes, and use make test to run the Crystal unit test suite and JavaScript linter before committing.

Where is the video player logic implemented?

The core player initialization resides in assets/js/player.js, which parses the JSON data injected by watch.ecr and configures Video.js instances. Hotkeys are handled in lines 61–78, while the options.controlBar.children array (lines 12–28) defines which controls appear in the player interface.

How are user preferences like volume and playback speed stored?

Preferences persist through a PREFS cookie that the server reads on subsequent requests. The updateCookie function in assets/js/player.js (lines 1088–1115) handles writing these values from JavaScript when users adjust settings, ensuring state survives page reloads.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →