npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

als-sitemap

v2.1.0

Published

Sitemap XML builder and parser based on als-document

Readme

als-sitemap

ESM sitemap builder and parser built on top of als-document. Runs in Node and in the browser.

The package default export is SitemapIndex. It is both the index sitemap class and the library namespace for helpers such as SitemapIndex.Sitemap, SitemapIndex.urlset(), SitemapIndex.parse(), and SitemapIndex.buildSitemaps().

Sitemap and SitemapIndex both extend XmlDocument, which extends Document from als-document. XML is built as a DOM tree, including the XML declaration node.

Install

npm install als-sitemap

Import

import SitemapIndex from 'als-sitemap'

const indexXml = new SitemapIndex(['/sitemaps/pages.xml'], { host: 'https://example.com' }).toString()
const urlsetXml = SitemapIndex.urlset(['/about'], { host: 'https://example.com' })

Named imports are also available:

import { Sitemap, buildSitemaps, parse, validators } from 'als-sitemap'

When working inside this repository, import from ./lib/index.mjs:

import SitemapIndex from './lib/index.mjs'

Browser

The whole library (and als-document) runs in the browser. npm run build bundles everything into self-contained files in dist/ (no Node built-ins, ~27 KB minified):

  • als-sitemap.esm.js / .esm.min.js — ES module
  • als-sitemap.iife.js / .iife.min.js — global alsSitemap for a <script> tag
<script src="als-sitemap.iife.min.js"></script>
<script>
  const xml = alsSitemap.urlset(['/about'], { host: 'https://example.com' })
</script>
import SitemapIndex, { parse } from './als-sitemap.esm.min.js'

Bundlers (Vite, webpack, esbuild) pick the browser bundle automatically through the browser export condition. Everything works in the browser except gzip: toGzip() and buildSitemaps({ gzip: true }) need Node and throw a clear error in the browser build. byteLength falls back to TextEncoder when Buffer is unavailable.

Static Helpers

SitemapIndex.Sitemap
SitemapIndex.SitemapIndex
SitemapIndex.namespaces
SitemapIndex.validators
SitemapIndex.urlset(entries, options)
SitemapIndex.index(entries, options)
SitemapIndex.parse(xml, options)
SitemapIndex.generate(data, options)
SitemapIndex.buildSitemaps(entries, options)
SitemapIndex.robots(options)
SitemapIndex.sitemap(entries, options)
SitemapIndex.sitemapIndex(entries, options)

Basic Urlset

import SitemapIndex from 'als-sitemap'

const sitemap = new SitemapIndex.Sitemap([
   {
      loc: 'https://example.com/',
      lastmod: new Date('2026-06-09T00:00:00Z'),
      changefreq: 'daily',
      priority: 0.8
   }
])

console.log(sitemap.toString())

Output starts with:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

You can also add entries one by one:

const sitemap = new SitemapIndex.Sitemap()
sitemap.add('https://example.com/')
sitemap.add({ loc: 'https://example.com/about', lastmod: '2026-06-09' })

const xml = sitemap.urlset()

One Host For Many Paths

If every URL belongs to the same site, pass the host once and use paths in entries.

const sitemap = new SitemapIndex.Sitemap([
   '/',
   '/about',
   { loc: '/blog/post-1', lastmod: '2026-06-09' }
], { host: 'https://example.com' })

console.log(sitemap.toString())

Generated <loc> values are absolute:

<loc>https://example.com/</loc>
<loc>https://example.com/about</loc>
<loc>https://example.com/blog/post-1</loc>

host, origin, and baseUrl are accepted option names. The host is validated once when the document is created. Relative paths are resolved before entry validation.

Validation

Validation is enabled by default. Invalid entries are skipped and errors are collected.

const sitemap = new SitemapIndex.Sitemap()

sitemap.add({ loc: 'not a url' })

console.log(sitemap.entries) // []
console.log(sitemap.errors)  // ['not a url is not a valid URL']

Disable validation with validate: false:

const sitemap = new SitemapIndex.Sitemap([{ loc: 'not a url' }], { validate: false })

entries and errors return copies, so external code cannot mutate internal state. Nested extension URLs (image.loc, image.license, video.thumbnail_loc, video.content_loc, video.player_loc, alternate.href) are validated too.

Image, Video And News

Namespaces are added only when the matching extension is used.

import SitemapIndex from 'als-sitemap'

const xml = new SitemapIndex.Sitemap([
   {
      loc: '/post',
      images: [
         {
            loc: '/image.jpg',
            caption: 'Preview',
            title: 'Image title',
            geo_location: 'Jerusalem, Israel',
            license: '/license'
         }
      ],
      videos: [
         {
            thumbnail_loc: '/thumb.jpg',
            title: 'Demo',
            description: 'Demo video',
            content_loc: '/video.mp4',
            player_loc: {
               loc: '/player',
               allow_embed: 'yes',
               autoplay: 'ap=1'
            },
            duration: 120,
            tags: ['demo', 'product']
         }
      ],
      news: {
         publication: { name: 'Example News', language: 'en' },
         publication_date: '2026-06-09',
         title: 'News title',
         keywords: 'example, sitemap'
      }
   }
], { host: 'https://example.com' }).toString()

The host is also applied to extension URL fields such as image.loc, image.license, video.thumbnail_loc, video.content_loc, video.player_loc, and video.uploader.info.

Videos support the full Google field set. Plain fields: duration, expiration_date, rating, view_count, publication_date, family_friendly, category, requires_subscription, live, tags. Fields that carry attributes use a { value, ...attributes } shape (a plain string is treated as value):

{
   restriction: { relationship: 'allow', value: 'US CA' },
   platform: { relationship: 'deny', value: 'tv' },
   uploader: { info: 'https://example.com/users/bob', value: 'Bob' },
   prices: [{ currency: 'EUR', type: 'rent', value: '1.99' }]
}

price is accepted as an alias for a single prices entry. News entries also support genres, keywords, and stock_tickers.

Localized Versions (hreflang)

Add alternates to an entry to emit xhtml:link rel="alternate" tags for localized pages. The xmlns:xhtml namespace is added automatically.

import SitemapIndex from 'als-sitemap'

const xml = new SitemapIndex.Sitemap([
   {
      loc: '/en/page',
      alternates: [
         { hreflang: 'en', href: '/en/page' },
         { hreflang: 'de', href: '/de/page' },
         { hreflang: 'x-default', href: '/' }
      ]
   }
], { host: 'https://example.com' }).toString()

lang/url are accepted as aliases for hreflang/href. href is resolved against host and validated like any other URL. Parsing returns the same alternates array on each url entry.

<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/page"/>

XSL Stylesheet

Pass xslUrl (or stylesheet) to emit an <?xml-stylesheet?> processing instruction so the sitemap renders in a browser. The href is resolved against host.

const xml = new SitemapIndex.Sitemap(['/'], { host: 'https://example.com', xslUrl: '/sitemap.xsl' }).toString()
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="https://example.com/sitemap.xsl"?>
<urlset ...>

Gzip And Size Limits

toGzip() returns a gzipped Buffer of the document (Node only). Available on both Sitemap and SitemapIndex.

const sitemap = new SitemapIndex.Sitemap(['/'], { host: 'https://example.com' })
const buffer = sitemap.toGzip()

The sitemap spec caps a single file at 50,000 entries and 50 MB. Both classes expose:

  • byteLength: UTF-8 byte size of the rendered document.
  • withinLimits: false when entries exceed maxEntries (50,000) or bytes exceed maxBytes (50 MB).

Adding a 50,001st entry also pushes a message into errors. The limits are static (Sitemap.maxEntries, Sitemap.maxBytes) and can be overridden in a subclass. Use buildSitemaps to split large sets automatically.

Sitemap Index

import SitemapIndex from 'als-sitemap'

const indexXml = new SitemapIndex([
   { loc: '/pages.xml', lastmod: '2026-06-09' },
   '/blog.xml'
], { host: 'https://example.com' }).toString()

Or use the helper:

const indexXml = SitemapIndex.index(['/pages.xml'], { host: 'https://example.com' })

Helpers

urlset(entries) is a shortcut for new Sitemap(entries).toString().

import SitemapIndex from 'als-sitemap'

const xml = SitemapIndex.urlset(['/'], { host: 'https://example.com' })

generate(data) returns an object with XML strings for each provided sitemap type.

import SitemapIndex from 'als-sitemap'

const result = SitemapIndex.generate({
   urlsSet: [{ loc: '/' }],
   index: [{ loc: '/pages.xml' }],
   image: [
      { page: '/post', loc: '/image.jpg' }
   ]
}, { host: 'https://example.com' })

console.log(result.urlsSet)
console.log(result.index)
console.log(result.image)

Build Multiple Sitemap Files

buildSitemaps(entries, options) groups entries, splits large groups, and creates an index file.

import SitemapIndex from 'als-sitemap'

const files = SitemapIndex.buildSitemaps([
   '/',
   '/about',
   '/blog/post-1',
   '/blog/post-2'
], { host: 'https://example.com', limit: 50000 })

console.log(Object.keys(files))
// ['pages.xml', 'blog.xml', 'sitemap.xml']

Options:

  • group(url, entry): returns the group name. Default groups top-level pages as pages, and nested paths by first path segment.
  • limit: max entries per sitemap file. Default is 50000.
  • origin: origin used for links in the index. Default is inferred from the first entry.
  • host or baseUrl: aliases for origin; useful when entries are relative paths.
  • dir: directory prefix for index links. Default is /.
  • indexName: index file name. Default is sitemap.xml.
  • fileName(name): file name for a group that was not split.
  • gzip: when true, every file (including the index) is gzipped. Keys gain a .gz suffix, values are Buffers, and index links point to the .gz files. (Node only.)
  • robots: when set, also emit a robots.txt. Entries with allow: false are excluded from the sitemaps and added as Disallow: lines, and a Sitemap: line points to the generated index. Pass true for defaults or an object ({ userAgent, allow, disallow, crawlDelay, host, sitemaps, groups, fileName }).

Custom grouping:

const files = SitemapIndex.buildSitemaps(entries, {
   host: 'https://example.com',
   group: (url, entry) => url.pathname.startsWith('/docs/') ? 'docs' : 'pages',
   dir: '/sitemaps/',
   indexName: 'sitemaps.xml',
   fileName: name => `${name}-sitemap.xml`
})

robots.txt

robots(options) builds a robots.txt from path-pattern rules. It is path-based (not a per-URL list), so it scales: block a whole section with one line.

import SitemapIndex from 'als-sitemap'

const txt = SitemapIndex.robots({
   host: 'https://example.com',
   sitemaps: ['/sitemap.xml'],
   groups: [
      { userAgent: '*', disallow: ['/admin', '/cart'], crawlDelay: 10 },
      { userAgent: ['Googlebot', 'Bingbot'], allow: ['/'] }
   ]
})
User-agent: *
Disallow: /admin
Disallow: /cart
Crawl-delay: 10

User-agent: Googlebot
User-agent: Bingbot
Allow: /

Host: https://example.com
Sitemap: https://example.com/sitemap.xml

A group with no allow/disallow means "allow all" (Disallow:). userAgent accepts an array to share rules across agents. Relative sitemaps are resolved against host.

buildSitemaps integrates with it: mark entries allow: false (the default is true, so you never set it on normal pages) to keep them out of the sitemaps and turn them into Disallow: lines, then pass robots to emit the file.

const files = SitemapIndex.buildSitemaps([
   '/',
   '/about',
   { loc: '/admin', allow: false }
], { host: 'https://example.com', robots: { crawlDelay: 5 } })

console.log(files['robots.txt'])
// User-agent: *
// Disallow: /admin
// Crawl-delay: 5
//
// Sitemap: https://example.com/sitemap.xml

Parse XML

import SitemapIndex from 'als-sitemap'

const xml = SitemapIndex.urlset([
   {
      loc: 'https://example.com/post',
      images: [{ loc: 'https://example.com/image.jpg', caption: 'A & B' }]
   }
])

const result = SitemapIndex.parse(xml)

console.log(result.urlsSet[0].loc)
console.log(result.image[0])

Parsed result shape:

{
   errors: [],
   urlsSet: [
      {
         loc: 'https://example.com/post',
         images: [{ loc: 'https://example.com/image.jpg', caption: 'A & B' }]
      }
   ],
   index: [],
   image: [
      { page: 'https://example.com/post', loc: 'https://example.com/image.jpg', caption: 'A & B' }
   ],
   video: [],
   news: []
}

Parse only selected parts:

SitemapIndex.parse(xml, { urlsSet: true })
SitemapIndex.parse(xml, { image: true })
SitemapIndex.parse(xml, { index: true })

Static Values And Validators

import SitemapIndex from 'als-sitemap'

console.log(SitemapIndex.Sitemap.namespaces === SitemapIndex.namespaces) // true
console.log(SitemapIndex.validators.isValidURL('https://example.com/')) // true
console.log(SitemapIndex.validators.isValidDate('2026-06-09')) // true
console.log(SitemapIndex.validators.isValidPriority(0.5)) // true

Available namespaces:

SitemapIndex.namespaces.sitemap
SitemapIndex.namespaces.image
SitemapIndex.namespaces.video
SitemapIndex.namespaces.news
SitemapIndex.namespaces.xhtml

Entry Shape

Url entry:

{
   loc: 'https://example.com/',
   lastmod: '2026-06-09',
   changefreq: 'daily',
   priority: 0.8,
   images: [],
   videos: [],
   news: {},
   alternates: []
}

Sitemap index entry:

{
   loc: 'https://example.com/pages.xml',
   lastmod: '2026-06-09'
}

Scripts

  • npm test — run the test suite.
  • npm run coverage — run tests with coverage.
  • npm run build — build the browser bundles into dist/.

License

MIT