GitHub composite action that generates a sitemap.xml from any PostgREST/Supabase relation and writes it into your repository, so it can be committed or deployed with your static site.
jobs:
generate-sitemap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: AFischbeck/supabase-sitemap-action@v1
with:
supabase-url: ${{ secrets.SUPABASE_URL }}
supabase-key: ${{ secrets.SUPABASE_KEY }}
base-url: https://your-site.com
output-dir: public
table: posts_with_categories
filter: is_public=eq.true
fixed-routes: '[{"path": "/", "changefreq": "weekly", "priority": "1.0"}]'
dynamic-routes: '[{"mode": "row", "path": "/post/{slug}", "changefreq": "monthly", "priority": "0.8", "lastmod": "created_at"}, {"mode": "value", "column": "categories", "path": "/category/{value}", "changefreq": "weekly", "priority": "0.6"}]'| Input | Required | Default | Description |
|---|---|---|---|
supabase-url |
no* | — | Supabase project URL |
supabase-key |
no* | — | Supabase API key (anon or service role) |
base-url |
yes | — | Base URL of the site |
table |
no* | — | PostgREST relation (table or view) to fetch rows from; required when dynamic-routes is set |
dynamic-routes |
no | [] |
JSON array of route templates (see below); empty means the sitemap is fixed-route-only and no API call is made |
select |
no | derived | Comma-separated columns to fetch; defaults to all columns referenced by dynamic-routes |
filter |
no | — | PostgREST predicate(s), e.g. is_public=eq.true. &-separated predicates are supported. |
order |
no | — | PostgREST order expression(s), e.g. created_at.desc |
api-path |
no | /rest/v1 |
PostgREST API path |
limit |
no | — | Maximum number of rows to fetch (PostgREST caps results, so set this if you have many rows) |
output-dir |
no | public |
Directory to write sitemap.xml into |
fixed-routes |
no | [] |
JSON array of {"path", ...} objects, emitted verbatim (changefreq, priority, and lastmod optional) |
* table, supabase-url, and supabase-key are only needed when dynamic-routes is set; a fixed-route-only sitemap runs without them.
Each dynamic-routes entry is either a per-row URL or a per-distinct-value URL.
Row mode — one URL per fetched row; {column} placeholders are substituted from each row. Rows missing a column or containing an empty value are skipped. lastmod is an optional column name, truncated to the date (set "lastmod_full": true to emit it verbatim):
{"mode": "row", "path": "/post/{slug}", "changefreq": "monthly", "priority": "0.8", "lastmod": "created_at"}Value mode — one URL per distinct value of a column. Column values may be text arrays or scalars; nulls are ignored. Values are lowercased by default; set "lowercase": false to disable. The path must contain the {value} placeholder:
{"mode": "value", "column": "categories", "path": "/category/{value}", "changefreq": "weekly", "priority": "0.6"}changefreq and priority are optional in both modes.
MIT