# How to Format M3U Playlist Files for IPTV: The Complete Technical Guide

> Learn to format M3U playlist files for IPTV using the Extended M3U specification with XMLTV attributes. Master iptv-org/iptv standards for efficient streaming.

- Repository: [iptv-org/iptv](https://github.com/iptv-org/iptv)
- Tags: how-to-guide
- Published: 2026-02-25

---

**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`](https://github.com/iptv-org/iptv/blob/main/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 `-1` for 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 `#EXTINF` line
- **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`](https://github.com/iptv-org/iptv/blob/main/scripts/utils.ts). The `normalizeURL(url: string): string` function ensures consistency and prevents duplicate entries caused by trivial variations:

```typescript
// 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`](https://github.com/iptv-org/iptv/blob/main/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:

```m3u
#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:

```typescript
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:

```m3u
#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`](https://github.com/iptv-org/iptv/blob/main/m3u-linter.json) before submitting.

## Summary

- **M3U playlists** require the `#EXTM3U` header and follow the Extended M3U specification with XMLTV attributes
- **Essential tags** include `tvg-id` (unique identifier), `tvg-logo` (absolute URL), and `group-title` (categorization)
- **Source files** live in `streams/` and are concatenated into master indexes like `index.category.m3u`
- **URL normalization** via [`scripts/utils.ts`](https://github.com/iptv-org/iptv/blob/main/scripts/utils.ts) prevents duplicates by decoding percent-encoding and replacing spaces with `+`
- **Validation** occurs through [`m3u-linter.json`](https://github.com/iptv-org/iptv/blob/main/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`](https://github.com/iptv-org/iptv/blob/main/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`](https://github.com/iptv-org/iptv/blob/main/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`](https://github.com/iptv-org/iptv/blob/main/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`](https://github.com/iptv-org/iptv/blob/main/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`](https://github.com/iptv-org/iptv/blob/main/scripts/utils.ts) specifically processes absolute URLs to ensure they remain valid across all distribution endpoints.