What Is OPML?
OPML stands for Outline Processor Markup Language — an XML dialect invented by Dave Winer at UserLand Software in 2000 for storing hierarchical outlines. While it was originally designed as a generic outline format (the "O" in OPML), it became famous almost entirely through one killer application: podcast and RSS feed subscription lists.
When you move from one podcast app to another — say from Pocket Casts to Overcast — OPML is the file format you export and import to carry all your subscriptions with you. It is similarly used by RSS readers (Feedly, NewsBlur, Inoreader) to transfer entire feed libraries in a single click. Underneath the branding of "export subscriptions," the file format is OPML every time.
Structure of an OPML File
OPML is valid XML with a two-part structure: a <head> section with document metadata and a <body> containing nested <outline> elements.
<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
<head>
<title>My Podcast Subscriptions</title>
<dateCreated>Sat, 25 Apr 2026 10:00:00 GMT</dateCreated>
<ownerName>Jane Smith</ownerName>
</head>
<body>
<outline text="Technology" title="Technology">
<outline type="rss"
text="Syntax FM"
title="Syntax FM"
xmlUrl="https://feed.syntax.fm/rss"
htmlUrl="https://syntax.fm/" />
<outline type="rss"
text="The Changelog"
title="The Changelog"
xmlUrl="https://changelog.com/podcast/feed"
htmlUrl="https://changelog.com/podcast" />
</outline>
<outline text="Science" title="Science">
<outline type="rss"
text="Radiolab"
title="Radiolab"
xmlUrl="https://feeds.wnyc.org/radiolab"
htmlUrl="https://radiolab.org/" />
</outline>
</body>
</opml>
The key attributes for podcast/RSS outlines are:
| Attribute | Purpose |
|---|---|
type |
"rss" for feeds, "link" for URLs, omitted for folders |
text |
Display name (shown in UI) |
title |
Same as text (legacy duplication) |
xmlUrl |
The actual feed URL — the most important attribute |
htmlUrl |
Link to the website/show notes |
description |
Optional short description of the feed |
category |
Tag or category string (space-separated) |
The nested structure naturally creates folder grouping — a great feature when you subscribe to 200 podcasts and want them organized by topic.
OPML Versions
OPML 1.0 (2000) — the original spec from UserLand. Loose and informal. Many apps still produce version 1.0 OPML for maximum compatibility.
OPML 2.0 (2007) — added the <expansionState>, <vertScrollState>, and <windowXxx> elements in the head for saving UI state, plus formalized the type attribute. The dateCreated / dateModified / ownerName / ownerId / docs head elements were also solidified.
In practice the difference is minor. Both versions are understood by all major podcast and RSS applications. The version number appears in the root <opml version="X"> attribute.
Common Uses of OPML
Podcast Subscription Export/Import
This is the dominant use. Every major podcast app supports it:
- Export: Settings → Export Subscriptions → Save OPML file
- Import: Settings → Import OPML → select file
Supported by: Apple Podcasts, Overcast, Pocket Casts, Castro, Downcast, AntennaPod (Android), Podcast Addict, and dozens more. The OPML file travels between ecosystems and between iOS/Android without friction.
RSS Reader Migration
Feedly, NewsBlur, Inoreader, FreshRSS, and Miniflux all export and import OPML. A power user curating 500 RSS feeds can move to a self-hosted reader in minutes using a single OPML file.
Outlines and Documentation
Some writers use OPML-native apps (OmniOutliner, WorkFlowy, Bike, Logseq) to organize documents as nested outlines. OPML is the interchange format between these tools. The indented structure maps naturally to chapters, sections, and bullet points.
Technical Note: OPML Is Not a Podcast Directory
OPML does not contain episode audio, artwork, or metadata — it only contains feed URLs. The podcast app uses those URLs to fetch the actual RSS feed and discover episodes. OPML is purely a contact list for feeds, not a content archive.
Working with OPML Files
Command-Line Parsing
Since OPML is XML, any XML tool works:
# Extract all feed URLs from an OPML file
xmllint --xpath '//outline[@type="rss"]/@xmlUrl' subscriptions.opml | \
tr ' ' '\n' | sed 's/xmlUrl="//;s/"//'
# Count how many feeds are in the OPML
grep -c 'xmlUrl=' subscriptions.opml
# Convert OPML to a plain text list of feed URLs
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('subscriptions.opml')
for outline in tree.iter('outline'):
url = outline.get('xmlUrl')
if url:
print(url)
" > feed_list.txt
Validating OPML
The official OPML 2.0 schema is loose — there is no strict DTD enforced. The most common validation issues are:
- Missing
xmlUrl— an outline withtype="rss"but no feed URL is useless to importing apps - Invalid XML encoding — podcast titles with special characters (
&,<,>) must be XML-escaped:&,<,> - Duplicate entries — some export tools produce duplicate outlines; importing apps may deduplicate or warn
Merging OPML Files
If you maintain feeds across multiple devices or apps, you may end up with several OPML files. Python's xml.etree.ElementTree makes merging straightforward:
import xml.etree.ElementTree as ET
def merge_opml(files, output):
base = ET.parse(files[0])
body = base.find('body')
seen = {o.get('xmlUrl') for o in body.iter('outline') if o.get('xmlUrl')}
for f in files[1:]:
tree = ET.parse(f)
for outline in tree.find('body'):
url = outline.get('xmlUrl')
if url and url not in seen:
body.append(outline)
seen.add(url)
base.write(output, encoding='utf-8', xml_declaration=True)
merge_opml(['feeds_a.opml', 'feeds_b.opml'], 'merged.opml')
Converting OPML
OPML → plain text feed list: extract all xmlUrl attributes (see Python snippet above). Useful for feeding into a feed aggregator or newsletter tool.
OPML → Markdown outline: convert the nested <outline> tree to indented # headings or bullet points. Tools like pandoc do not natively support OPML, but a short Python or XSLT script handles it trivially.
CSV/JSON → OPML: if you have a spreadsheet of podcasts with feed URLs, generating OPML is simple — wrap each row in an <outline> element with the appropriate attributes.
OPML → FOLIO / OMPL: no widely-used successor has replaced OPML. Proposals like FOLIO existed but never gained traction. OPML remains the de facto standard after 25 years.
Why OPML Has Lasted 25 Years
OPML succeeds because it is extraordinarily simple. It is plain UTF-8 XML. Any text editor can open it. Any programming language can parse it in five lines of code. It has no DRM, no binary encoding, no proprietary lock-in. The single required piece of information — the feed URL — fits naturally into one XML attribute.
The format's longevity is a case study in "worse is better": OPML is not elegant XML, it is not formally specified, and it has quirks — but it works everywhere, forever, with zero dependencies.
Best Practices for OPML Files
- Organize with folders: Group feeds by category using nested
<outline>elements withouttype="rss". Most apps will show these as folders or categories. - Include
description: A short description attribute helps when reviewing subscriptions months later. - Update
dateModified: Keep the head element accurate so you know which export is most recent when you have multiple backup files. - Keep backups: Export your OPML quarterly. Podcast apps disappear, accounts get deleted, and OPML is the only way to recover a carefully curated library.
- Deduplicate before importing: A duplicate feed wastes bandwidth and clutters your app. Clean the OPML before importing if merging multiple sources.
Related conversions
Audio format pairs that come up most often: