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

InputRequiredDefaultDescription
api-tokenYesSitepaste API token. Store it as a repository secret.
content-dirNocontentDirectory containing the Markdown files.
content-typeNodocsContent type for all pages: docs, blog, or standalone.
site-idNoTarget site, as the short ID shown in the dashboard or the full UUID. Defaults to your workspace’s default site.
dry-runNofalseValidate and preview without publishing or deploying.
pruneNofalseDelete pages of this content type that have no matching file, making the directory the source of truth.
fail-on-build-errorNotrueFail 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.

FieldDescription
slugURL slug for the page.
titlePage title.
contentTypedocs, blog, standalone, or homepage. Defaults to the workflow’s content-type input. A homepage file publishes at / and must sit at the content root.
sectionOverrides the directory-based section.
descriptionMeta description.
api_endpointMarks the page as an API reference, like GET /sites/{siteId}/pages. See API reference pages.
draftSet to true to keep the page off the deployed site.
show_listingsHomepage only: whether recent posts and section listings show below the content. true or false.
tagsA YAML list of tags. Casing is kept as the display name: iOS is stored as ios and displays as iOS.
date or publishedAtPublish date. Date-only values like 2026-02-19 are accepted.
authorAuthor 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_urlSocial preview image URL.
languageLanguage 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_positionPer-page theme overrides. Empty string resets a field to inherit from the site.
show_tocTable of contents.
show_social_shareSocial share buttons.
show_commentsComments.
show_next_prevNext/previous post navigation.
show_newsletter_ctaNewsletter signup CTA.
show_tagsTag chips.
show_datesPublish dates.
show_authorAuthor byline.
show_reading_timeReading time estimate.
show_breadcrumbsBreadcrumbs.
show_copy_markdownCopy-as-Markdown button.
show_gallery_downloadGallery “Download all” button, which serves the gallery as a ZIP.
show_gallery_item_downloadDownload buttons on individual gallery photos and videos, in the grid and the lightbox.
full_width_galleryFull-width gallery grid (photographer theme).
masonry_galleryMasonry 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 ![](./photo.jpg) 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.