How to Format M3U Playlist Files for IPTV: The Complete Technical Guide
M3U playlist files for IPTV follow the Extended M3U specification with XMLTV attributes such as tvg-id, tvg-logo, and group-title added to #EXTINF lines, requiring a #EXTM3U header and normalized stream URLs as implemented in the iptv-org/iptv repository.
The iptv-org/iptv repository maintains one of the largest collections of publicly available IPTV streams, storing every channel as a plain-text M3U playlist. Understanding how to format these files correctly ensures compatibility with popular players like VLC, Kodi, and Perfect Player while maintaining consistency with the project's automated validation pipelines.
Understanding the Extended M3U Structure
The Extended M3U specification extends the basic playlist format to support metadata attributes, forming the foundation of modern IPTV distribution.
The #EXTM3U Header
Every valid playlist must begin with the #EXTM3U directive on the first line. This marker identifies the file as an Extended M3U playlist and signals to parsers that subsequent lines may contain tagged metadata. In the iptv-org repository, omitting this header causes the CI linter defined in m3u-linter.json to reject the file immediately.
The #EXTINF Directive and Stream URLs
Each channel entry consists of two consecutive lines: a metadata directive followed by the stream URL. The #EXTINF line uses the format #EXTINF:<duration> <attributes>,<display-name>, where:
- duration is typically
-1for live television streams - attributes are space-separated key-value pairs (e.g.,
tvg-id="ABC.us") - display-name appears after the comma and serves as the fallback channel name
The URL line immediately follows without any prefix, pointing directly to an HLS stream (.m3u8) or other supported format.
Essential IPTV-Specific Metadata Tags
The repository implements the XMLTV schema for interoperability with electronic program guides. While the specification supports many attributes, these are the critical tags used throughout streams/us.m3u and other source files:
- tvg-id: A unique identifier often formatted as
<channel>.<region>@<source>(e.g.,ABC.us@East) that links the stream to EPG data - tvg-name: The human-readable display name; if omitted, players default to the text after the comma in the
#EXTINFline - tvg-logo: An absolute URL pointing to the channel's logo image (PNG, JPG, or SVG)
- group-title: Used for logical categorization into categories, languages, countries, or regions (e.g.,
News,Sports,English) - tvg-country and tvg-language: ISO codes providing additional geographic and linguistic metadata consumed by advanced players
When a tag is missing, IPTV applications fall back to default behaviors, but omitting tvg-id or group-title removes the channel from filtered views in the generated index files.
Repository Structure and Build Process
The iptv-org project organizes playlists through a specific directory hierarchy and automated processing pipeline that enforces formatting standards.
Directory Organization
Individual channel lists reside in the streams/ directory, partitioned by geographic or logical boundaries. Files such as streams/us.m3u, streams/ca.m3u, and streams/categories/news.m3u contain the raw #EXTINF entries. The build system concatenates these source files to generate master playlists including index.category.m3u, index.language.m3u, and index.country.m3u, preserving the original order of entries.
URL Normalization
Before writing URLs to any playlist, the repository applies strict normalization through scripts/utils.ts. The normalizeURL(url: string): string function ensures consistency and prevents duplicate entries caused by trivial variations:
// scripts/utils.ts – canonicalizes URLs before insertion
import normalizeUrl from 'normalize-url'
export function normalizeURL(url: string): string {
const normalized = normalizeUrl(url, { stripWWW: false })
return decodeURIComponent(normalized).replace(/\s/g, '+')
}
This process percent-decodes the URL and replaces whitespace characters with plus signs, creating a canonical form that the linter recognizes as valid.
Validation with m3u-linter.json
The m3u-linter.json configuration defines required fields and acceptable value patterns for the entire collection. The CI workflow runs this linter against every pull request, verifying that each playlist contains the mandatory #EXTM3U header, maintains unique tvg-id values within its scope, and references properly formatted URLs.
Practical Implementation Examples
These examples demonstrate the exact formatting standards required for contributions to the iptv-org repository.
Minimal Hand-Crafted Playlist
A valid entry requires only the header, an #EXTINF line with minimal attributes, and a stream URL:
#EXTM3U
#EXTINF:-1 tvg-id="NewsChannel.us@US" tvg-name="Global News" tvg-logo="https://example.com/logo.png" group-title="News",Global News
https://cdn.example.com/live/news.m3u8
Adding tvg-id and group-title enables the channel to appear in TV guides and category filters across the generated index playlists.
Generating Playlists Programmatically
When building playlists dynamically, import the repository's normalization utility to ensure compliance:
import fs from 'fs'
import { normalizeURL } from './scripts/utils.js'
const channels = [
{
id: 'ABC.us@East',
name: 'ABC',
logo: 'https://example.com/abc.png',
group: 'Entertainment',
url: 'http://41.205.93.154/ABC/index.m3u8',
}
]
let m3u = '#EXTM3U\n'
for (const ch of channels) {
const cleanUrl = normalizeURL(ch.url)
m3u += `#EXTINF:-1 tvg-id="${ch.id}" tvg-name="${ch.name}" tvg-logo="${ch.logo}" group-title="${ch.group}",${ch.name}\n`
m3u += `${cleanUrl}\n`
}
fs.writeFileSync('my_playlist.m3u', m3u)
This script mirrors the official generator logic: writing the header, normalizing URLs via the normalizeURL function, and including full XMLTV attribute sets.
Contributing New Channels
To add a channel to an existing regional playlist such as streams/us.m3u, insert a new block before the target position:
#EXTINF:-1 tvg-id="NewChannel.us@US" tvg-name="New Channel" tvg-logo="https://example.com/new.png" group-title="Sports",New Channel
https://live.example.com/newchannel.m3u8
After editing, run the local linter or CI check to verify compliance with m3u-linter.json before submitting.
Summary
- M3U playlists require the
#EXTM3Uheader and follow the Extended M3U specification with XMLTV attributes - Essential tags include
tvg-id(unique identifier),tvg-logo(absolute URL), andgroup-title(categorization) - Source files live in
streams/and are concatenated into master indexes likeindex.category.m3u - URL normalization via
scripts/utils.tsprevents duplicates by decoding percent-encoding and replacing spaces with+ - Validation occurs through
m3u-linter.json, which enforces structural requirements in the CI pipeline
Frequently Asked Questions
What is the difference between M3U and M3U8 file formats?
M3U8 is simply a UTF-8 encoded variant of the M3U format. While M3U files may use Latin-1 or other encodings, the iptv-org repository exclusively uses UTF-8 encoding (M3U8) to support international channel names and ensures all stream URLs are properly normalized through the normalizeURL function in scripts/utils.ts.
Is the tvg-id attribute required for all channels?
While the M3U specification makes all attributes optional, the iptv-org m3u-linter.json configuration requires tvg-id for proper EPG integration. Channels without this tag may be flagged during CI validation and will not appear in guide-based filtering within the generated index.category.m3u and related master playlists.
How does iptv-org validate playlist formatting?
The repository uses a linter defined in m3u-linter.json that runs automatically via GitHub Actions. This tool checks for the presence of the #EXTM3U header, validates that tvg-id values are unique within a playlist, verifies URL accessibility, and ensures all entries follow the normalized format produced by scripts/utils.ts.
Can I use relative URLs in M3U playlist files?
No, the iptv-org repository requires absolute URLs for all stream sources. Relative paths break when playlists are concatenated into the master index files (such as index.country.m3u). The normalizeURL function in scripts/utils.ts specifically processes absolute URLs to ensure they remain valid across all distribution endpoints.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →