GitHub Actions
The Sitepaste deploy action publishes a directory of Markdown files from your repository and deploys your site. Push to main, and your changes are live in seconds. It is the natural fit for docs that live alongside code, and it works just as well for a blog kept in a repo.
Using the action requires an API token, which is a Pro plan feature.
Quick start
Create an API token in the Sitepaste dashboard under Account > Tokens (see authentication), and add it to your repository as a secret named SITEPASTE_TOKEN. The token needs the content and deploy scopes. content writes the pages through POST /sites/{siteId}/pages/batch, and deploy publishes them through POST /sites/{siteId}/deployments, which the action posts once the pages have landed. A token holding content alone still syncs every page, and the run then ends with the deploy refused, which is what fail-on-build-error reports.
Then add a workflow at .github/workflows/deploy.yml:
name: Deploy docs
on:
push:
branches: [main]
paths: [docs/**]
concurrency:
group: sitepaste-deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sitepaste/integrations/actions/deploy@v1
with:
api-token: ${{ secrets.SITEPASTE_TOKEN }}
content-dir: docs
content-type: docs
Every run validates all files, publishes them in one batch, and triggers a deploy.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
| api-token | Yes | Sitepaste API token. Store it as a repository secret. | |
| content-dir | No | content | Directory containing the Markdown files. |
| content-type | No | docs | Content type for all pages: docs, blog, or standalone. |
| site-id | No | Target site, as the short ID shown in the dashboard or the full UUID. Defaults to your workspace’s default site. | |
| dry-run | No | false | Validate and preview without publishing or deploying. |
| prune | No | false | Delete pages of this content type that have no matching file, making the directory the source of truth. |
| fail-on-build-error | No | true | Fail the run when the pages were saved but the deploy was refused. |
Outputs: page-count (pages published) and deploy-url (the deployed site’s URL).
Sections from directories
The action maps your directory structure to sections. Files at the root of the content directory have no section; files in a subdirectory use the subdirectory name:
docs/
overview.md → /docs/overview
guides/
getting-started.md → /docs/guides/getting-started
deployment.md → /docs/guides/deployment
With content-type: standalone, the same layout produces top-level paths instead: guides/getting-started.md is published at /guides/getting-started. Standalone sections may also nest one level. A second directory becomes a sub-section:
api/
overview.md → /api/overview
builds/
post-builds.md → /api/builds/post-builds
Directories beyond that depth (one level for docs and blog, two for standalone) are ignored for the section, and the action prints a warning. A section field in front matter overrides the directory, and may itself be a nested path like api/builds on standalone pages. Set it to an empty string to clear the section for a file inside a subdirectory.
Directory casing carries through as the section’s display name: a directory named API/ publishes at /api/ and displays as “API” in the navigation, exactly as if you had typed it in the dashboard. Captured casing only fills in a display name where none is set, so a rename made in the dashboard survives every deploy.
Front matter
All fields are optional. Without front matter, the slug comes from the filename and the title from the slug.
| Field | Description |
|---|---|
| slug | URL slug for the page. |
| title | Page title. |
| contentType | docs, blog, standalone, or homepage. Defaults to the workflow’s content-type input. A homepage file publishes at / and must sit at the content root. |
| section | Overrides the directory-based section. |
| description | Meta description. |
| api_endpoint | Marks the page as an API reference, like GET /sites/{siteId}/pages. See API reference pages. |
| draft | Set to true to keep the page off the deployed site. |
| show_listings | Homepage only: whether recent posts and section listings show below the content. true or false. |
| tags | A YAML list of tags. Casing is kept as the display name: iOS is stored as ios and displays as iOS. |
| date or publishedAt | Publish date. Date-only values like 2026-02-19 are accepted. |
| author | Author to credit the page to, as an author ID from GET /sites/{siteId}/authors (not a name, because names are not unique). Empty string removes the author. |
| og_image_url | Social preview image URL. |
| language | Language tag for the page, like en or pt-BR. |
| theme, primary_color, primary_color_dark, font_size, code_theme_light, code_theme_dark, gallery_download_position | Per-page theme overrides. Empty string resets a field to inherit from the site. |
| show_toc | Table of contents. |
| show_social_share | Social share buttons. |
| show_comments | Comments. |
| show_next_prev | Next/previous post navigation. |
| show_newsletter_cta | Newsletter signup CTA. |
| show_tags | Tag chips. |
| show_dates | Publish dates. |
| show_author | Author byline. |
| show_reading_time | Reading time estimate. |
| show_breadcrumbs | Breadcrumbs. |
| show_copy_markdown | Copy-as-Markdown button. |
| show_gallery_download | Gallery “Download all” button, which serves the gallery as a ZIP. |
| show_gallery_item_download | Download buttons on individual gallery photos and videos, in the grid and the lightbox. |
| full_width_gallery | Full-width gallery grid (photographer theme). |
| masonry_gallery | Masonry gallery grid (photographer theme). |
The boolean theme overrides above are tri-state: true, false, or "inherit" to fall back to the site setting. Front matter names them in snake_case; the API names the same fields in camelCase.
The password field is not supported here. Front matter is committed to the repository, so a page password written there would be readable by anyone with repo access and preserved in git history, which means it would not be a secret. The action fails the run if it finds one; set page passwords in the dashboard or through the API instead.
Validation
Every file is checked before anything is sent. The action verifies slug format and length, the presence and length of the title and description, the content size, the number of tags, and the dates, and it catches duplicate slugs across your files. If any check fails, the workflow fails with an annotation pointing at the exact file, and nothing is published.
Publishing
Each run sends the full set of files as one batch. Pages are matched by slug, so pages that already exist are updated in place rather than duplicated.
Deleting removed pages
By default, a file you remove from the repository stays published on your site. Set prune: 'true' to make the directory the source of truth. Pages of the deployed content type that have no matching file are then deleted in the same run, so removed and renamed files clean up after themselves.
Use pruning only when the repository owns that content type, because it also deletes pages of the same type that were created in the dashboard or published from Obsidian. Combine it with dry-run to preview what would be deleted before you commit to it.
Deploys
Every run that is not a dry run ends by triggering a deploy, which counts against your monthly deploy quota. Set dry-run: 'true' on pull requests to check your changes without publishing or deploying them.
The pages and the deploy are two requests, and the deploy goes second, so the pages are saved before the deploy is attempted. That means the deploy can be refused on its own, by a token without the deploy scope, the monthly quota, the 30-second cooldown, or the hourly deploy budget that every token in the workspace shares. When that happens the pages are saved but your site keeps serving its previous build, so the run fails with the reason rather than passing quietly. Set fail-on-build-error: 'false' to report it as a warning instead, which suits a repository that deploys on a schedule rather than on every push.
Images
Images are not published by the action. A relative reference like  is left as it is, and the action warns about it. Upload your images through the dashboard or the media API and reference them by URL instead.
The action source lives in the integrations repository. For the underlying API, see the pages and deployments reference.