Sitemap generation: streaming, parallel and incremental.
defmodule MyApp.Sitemaps do
use SiteMaps, otp_app: :my_app, adapter: {SiteMaps.Adapters.FileSystem, directory: "priv/sitemaps"}
process :static
process :posts, "posts/%{year}-%{month}/sitemap.xml",
args: [year: :integer, month: :integer],
defaults: [year: &MyApp.Clock.year/0, month: &MyApp.Clock.month/0]
@impl SiteMaps
def urls(:static, _args, _context), do: [url("/", priority: 1.0)]
def urls(:posts, %{year: year, month: month}, _context) do
MyApp.Posts.stream(year, month) |> Stream.map(&url("/posts/#{&1.slug}", lastmod: &1.updated_at))
end
end
# config/runtime.exs
config :my_app, MyApp.Sitemaps, url: "https://example.com/sitemap.xml.gz"
SiteMaps.generate(MyApp.Sitemaps)
SiteMaps.generate(MyApp.Sitemaps, only: [posts: [year: 2026, month: 3]])Every process returns an Enumerable of SiteMaps.URL entries, consumed lazily and written to
files that rotate at the sitemap protocol limits. Processes run in parallel, and runs that only
regenerate some processes keep the other entries of the existing index.
For several sites in one app, pass a :context identifying the site to generate/2 and
implement config/1 to build each site's options from it. See
multitenancy_phoenix for host→site→theme
Phoenix apps.
Summary
Callbacks
Returns the configuration options for context. See SiteMaps.Config.
Sitemaps added to the index as they are, e.g. [{"https://cdn.example.com/legacy.xml", ~D[2026-01-01]}].
Builds the context from its serialized form (mix task and Oban arguments).
Drops (nil) or replaces every URL before it is written.
Returns the URLs of the process name executed with args.
Functions
Defines a sitemap module.
Builds the validated configuration of module for context: the use SiteMaps options, then
the application environment (:otp_app), then config/1 when the module defines it.
Generates the sitemaps of module.
Same as generate/2 but raises on errors.
Declares a process.
Declared processes of module.
Builds a SiteMaps.URL. Paths are resolved against the site origin.
Callbacks
Returns the configuration options for context. See SiteMaps.Config.
@callback external_sitemaps(context :: map()) :: [ {String.t(), DateTime.t() | Date.t() | nil} | String.t() ]
Sitemaps added to the index as they are, e.g. [{"https://cdn.example.com/legacy.xml", ~D[2026-01-01]}].
Builds the context from its serialized form (mix task and Oban arguments).
@callback url_filter(SiteMaps.URL.t(), context :: map()) :: SiteMaps.URL.t() | nil
Drops (nil) or replaces every URL before it is written.
@callback urls(name :: atom(), args :: map(), context :: map()) :: Enumerable.t()
Returns the URLs of the process name executed with args.
Functions
Defines a sitemap module.
Configuration options (see SiteMaps.Config) can be given here, read from the application
environment with :otp_app, and returned by config/1. They are merged in that order, so
later sources win:
use SiteMaps, otp_app: :my_app, adapter: {SiteMaps.Adapters.FileSystem, directory: "priv/sitemaps"}
# config/runtime.exs
config :my_app, MyApp.Sitemaps, url: "https://example.com/sitemap.xml"config/1 is only needed when options depend on the context, e.g. one sitemap per site.
@spec config!(module(), map()) :: SiteMaps.Config.t()
Builds the validated configuration of module for context: the use SiteMaps options, then
the application environment (:otp_app), then config/1 when the module defines it.
@spec generate(module(), keyword()) :: {:ok, SiteMaps.Result.t()} | {:error, term()}
Generates the sitemaps of module.
Options
:context- map passed to every callback (default%{}):only- processes to run, e.g.[:static, posts: [year: 2026, month: 3]]. By default every process runs once with its default arguments.:prune- delete files of the regenerated processes that are no longer listed (defaultfalse):max_concurrency- overrides the configured concurrency
@spec generate!(module(), keyword()) :: SiteMaps.Result.t()
Same as generate/2 but raises on errors.
Declares a process.
process :static
process :news, "sitemap_news.xml"
process :posts, "posts/%{year}/sitemap.xml", args: [year: :integer], defaults: [year: 2026]The location is relative to the index directory and must interpolate every argument (%{arg}),
so each execution writes its own files. Without location, files are written next to the index as
sitemap_<name>1.xml, ... Two processes cannot share a location. Argument types are :integer, :string, :date, :boolean and
:atom; string values (from the CLI or job arguments) are cast. Defaults may be values or 0-arity
functions evaluated when the process is enqueued.
@spec processes(module()) :: [SiteMaps.Process.t()]
Declared processes of module.
@spec url(String.t(), keyword()) :: SiteMaps.URL.t()
Builds a SiteMaps.URL. Paths are resolved against the site origin.