Skip to content

Instantly share code, notes, and snippets.

@digitaldrreamer
Created August 19, 2026 21:35
Show Gist options
  • Select an option

  • Save digitaldrreamer/a78bc3eed34e6d668e580cd609ea4e42 to your computer and use it in GitHub Desktop.

Select an option

Save digitaldrreamer/a78bc3eed34e6d668e580cd609ea4e42 to your computer and use it in GitHub Desktop.
RSS structure for Dev.to

A guide to formatting an RSS 2.0 feed so DEV.to imports your articles, images, and tags with zero formatting errors.


1. Quick Checklist: What to Do

  • Wrap full article HTML inside <content:encoded><![CDATA[...]]></content:encoded>.
  • Convert all image src, srcset, and link href attributes to absolute URLs (https://domain.com/...).
  • Sanitize tags (<category>): alphanumeric only (a-z0-9), max 20 chars, max 4 tags per post.
  • Provide a unique <guid isPermaLink="true"> to prevent duplicate imports.
  • Provide cover images via <enclosure> and as the first <img> inside <content:encoded>.
  • In DEV Settings, keep "Mark RSS source as canonical" checked and "Replace self-referential links" unchecked.

2. RSS Field Reference

<item> Fields

Field Requirement What It Does on DEV.to
<title> Required Article headline. Escape XML special characters.
<link> Required Canonical link back to original post.
<guid> Required Unique ID. Prevents re-importing duplicates on sync.
<content:encoded> Required Full HTML body wrapped in <![CDATA[...]]>. Converted to Markdown by DEV.
<description> Fallback Excerpt/summary. Used as body only if <content:encoded> is missing.
<pubDate> Recommended UTC date (Wed, 19 Aug 2026 10:00:00 GMT). Sets publication/draft date.
<category> Optional (Max 4) Becomes post tags. Must be alphanumeric and $\le$ 20 chars.
<enclosure> Optional Sets the post's cover/hero image (type="image/webp" or image/png).
<author> Optional email@domain.com (Author Name)

3. Strict Formatting Rules

Rule 1: Tags (<category>)

DEV.to rejects or corrupts tags that don't match [a-zA-Z0-9]:

  • Remove special chars: next.js $\rightarrow$ nextjs, web-dev $\rightarrow$ webdev, c# $\rightarrow$ csharp.
  • Truncate: Maximum 20 characters per tag.
  • Limit: Maximum 4 tags per post.

Rule 2: Images & Links (Must Be Absolute)

DEV.to cannot resolve relative paths like /images/hero.png.

  • ❌ <img src="/images/hero.png">
  • ✅ <img src="https://yourdomain.com/images/hero.png">
  • ❌ <a href="/blog/my-post">
  • ✅ <a href="https://yourdomain.com/blog/my-post">

Rule 3: Cover Images

To ensure the cover image renders both as the header banner and in feed previews:

  1. Add <enclosure url="https://yourdomain.com/cover.webp" type="image/webp" length="0" />.
  2. Prepend <p><img src="https://yourdomain.com/cover.webp" alt="..." /></p> to <content:encoded>.

4. Gist Templates

Template A: Minimum Viable RSS Feed

The bare minimum needed for DEV.to to ingest a post cleanly:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>My Developer Blog</title>
    <link>https://example.com/blog</link>
    <description>Clean developer tutorials and guides.</description>

    <item>
      <title>How to Deploy Next.js to Cloudflare</title>
      <link>https://example.com/blog/nextjs-cloudflare</link>
      <guid isPermaLink="true">https://example.com/blog/nextjs-cloudflare</guid>
      <pubDate>Wed, 19 Aug 2026 10:00:00 GMT</pubDate>
      <content:encoded><![CDATA[
        <p>This is the full post body. All links like <a href="https://example.com/docs">Docs</a> must be absolute.</p>
      ]]></content:encoded>
    </item>
  </channel>
</rss>

Template B: Full Production RSS Feed (With Tags, Covers & CDATA)

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Engineering Blog</title>
    <link>https://example.com/blog</link>
    <description>Technical articles on web architecture and tooling.</description>
    <language>en-us</language>
    <lastBuildDate>Wed, 19 Aug 2026 21:00:00 GMT</lastBuildDate>
    <atom:link href="https://example.com/rss.xml" rel="self" type="application/rss+xml" />

    <item>
      <title>Building Scalable RSS Pipelines</title>
      <link>https://example.com/blog/scalable-rss-pipelines</link>
      <guid isPermaLink="true">https://example.com/blog/scalable-rss-pipelines</guid>
      <description>A guide to setting up automated RSS ingestion pipelines.</description>
      <content:encoded><![CDATA[
        <p><img src="https://example.com/images/cover.webp" alt="Building Scalable RSS Pipelines" /></p>
        <p>Full HTML content rendered here. Use absolute paths for all media assets.</p>
        <h2>Getting Started</h2>
        <p>Visit <a href="https://example.com/start">our onboarding guide</a> to learn more.</p>
      ]]></content:encoded>
      <pubDate>Wed, 19 Aug 2026 10:00:00 GMT</pubDate>
      <author>team@example.com (Author Name)</author>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>rss</category>
      <enclosure url="https://example.com/images/cover.webp" type="image/webp" length="0" />
    </item>
  </channel>
</rss>

5. Copy-Paste Code Utilities (TypeScript)

Use these helper functions when building your RSS feed generator:

// 1. Sanitize XML special characters
export function escapeXml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;")
}

// 2. Escape CDATA closing tags
export function escapeCdata(str: string): string {
  return str.replace(/]]>/g, "]]]]><![CDATA[>")
}

// 3. Format tags for DEV.to (Alphanumeric only, max 20 chars, max 4 tags)
export function formatDevTags(rawTags: string[]): string[] {
  const seen = new Set<string>()
  const result: string[] = []

  for (const raw of rawTags) {
    const clean = raw.replace(/[^a-zA-Z0-9]/g, "").slice(0, 20)
    const key = clean.toLowerCase()
    if (!clean || seen.has(key)) continue
    seen.add(key)
    result.push(clean)
    if (result.length === 4) break
  }
  return result
}

// 4. Convert all relative URLs (src, href, srcset) to absolute URLs
export function makeUrlsAbsolute(html: string, baseUrl: string): string {
  return html.replace(/<[a-zA-Z][^<>]*>/g, (tag) =>
    tag
      .replace(
        /\b(href|src)="(\/[^"]*)"/g,
        (_, attr, val) => `${attr}="${new URL(val, baseUrl).toString()}"`
      )
      .replace(
        /\bsrcset="([^"]*)"/g,
        (_, val) => `srcset="${val.replace(/\/[^\s,]*/g, (p) => new URL(p, baseUrl).toString())}"`
      )
  )
}

6. How to Configure DEV.to Settings

  1. Go to dev.to/settings/extensions.
  2. Scroll to "Publishing to DEV Community from RSS".
  3. Paste your feed URL (e.g. https://yourdomain.com/rss.xml).
  4. Set the options:
    • Mark the RSS source as canonical URL by default: [x] Checked (Preserves your Google SEO ranking).
    • Replace self-referential links with DEV Community-specific links: [ ] Unchecked (Keeps internal links pointing to your website).
  5. Click "Submit feed settings" $\rightarrow$ Click "Fetch feed now".
  6. Go to dev.to/dashboard to view, edit, and publish your imported drafts.
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Engineering Blog</title>
<link>https://example.com/blog</link>
<description>Technical articles on web architecture and tooling.</description>
<language>en-us</language>
<lastBuildDate>Wed, 19 Aug 2026 21:00:00 GMT</lastBuildDate>
<atom:link href="https://example.com/rss.xml" rel="self" type="application/rss+xml" />
<item>
<title>Building Scalable RSS Pipelines</title>
<link>https://example.com/blog/scalable-rss-pipelines</link>
<guid isPermaLink="true">https://example.com/blog/scalable-rss-pipelines</guid>
<description>A guide to setting up automated RSS ingestion pipelines.</description>
<content:encoded><![CDATA[
<p><img src="https://example.com/images/cover.webp" alt="Building Scalable RSS Pipelines" /></p>
<p>Full HTML content rendered here. Use absolute paths for all media assets.</p>
<h2>Getting Started</h2>
<p>Visit <a href="https://example.com/start">our onboarding guide</a> to learn more.</p>
]]></content:encoded>
<pubDate>Wed, 19 Aug 2026 10:00:00 GMT</pubDate>
<author>team@example.com (Author Name)</author>
<category>webdev</category>
<category>javascript</category>
<category>nextjs</category>
<category>rss</category>
<enclosure url="https://example.com/images/cover.webp" type="image/webp" length="0" />
</item>
</channel>
</rss>
// 1. Sanitize XML special characters
export function escapeXml(str: string): string {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;")
}
// 2. Escape CDATA closing tags
export function escapeCdata(str: string): string {
return str.replace(/]]>/g, "]]]]><![CDATA[>")
}
// 3. Format tags for DEV.to (Alphanumeric only, max 20 chars, max 4 tags)
export function formatDevTags(rawTags: string[]): string[] {
const seen = new Set<string>()
const result: string[] = []
for (const raw of rawTags) {
const clean = raw.replace(/[^a-zA-Z0-9]/g, "").slice(0, 20)
const key = clean.toLowerCase()
if (!clean || seen.has(key)) continue
seen.add(key)
result.push(clean)
if (result.length === 4) break
}
return result
}
// 4. Convert all relative URLs (src, href, srcset) to absolute URLs
export function makeUrlsAbsolute(html: string, baseUrl: string): string {
return html.replace(/<[a-zA-Z][^<>]*>/g, (tag) =>
tag
.replace(
/\b(href|src)="(\/[^"]*)"/g,
(_, attr, val) => `${attr}="${new URL(val, baseUrl).toString()}"`
)
.replace(
/\bsrcset="([^"]*)"/g,
(_, val) => `srcset="${val.replace(/\/[^\s,]*/g, (p) => new URL(p, baseUrl).toString())}"`
)
)
}
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>My Developer Blog</title>
<link>https://example.com/blog</link>
<description>Clean developer tutorials and guides.</description>
<item>
<title>How to Deploy Next.js to Cloudflare</title>
<link>https://example.com/blog/nextjs-cloudflare</link>
<guid isPermaLink="true">https://example.com/blog/nextjs-cloudflare</guid>
<pubDate>Wed, 19 Aug 2026 10:00:00 GMT</pubDate>
<content:encoded><![CDATA[
<p>This is the full post body. All links like <a href="https://example.com/docs">Docs</a> must be absolute.</p>
]]></content:encoded>
</item>
</channel>
</rss>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment