Publish with 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.

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. 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.

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.

Only one directory level is used. A file nested deeper keeps the top-level directory as its section, and the action prints a warning. A section field in front matter overrides the directory; set it to an empty string to clear the section for a file inside a subdirectory.

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.
sectionOverrides the directory-based section.
descriptionMeta description.
draftSet to true to keep the page off the deployed site.
tagsA YAML list of tags.
date or publishedAtPublish date. Date-only values like 2026-02-19 are accepted.

What the action does and does not do

It validates before sending. Slug format and length, title, description, and content size limits, tag limits, duplicate slugs, and dates are all checked locally first. If anything fails, the workflow fails with an annotation pointing at the exact file, and nothing is published.

It publishes everything, every run. Each run sends the full set of files as one batch. Pages are matched by slug, so existing pages are updated in place.

It does not delete unless you ask. 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 with no matching file are deleted in the same run, so removed and renamed files clean up after themselves. Use pruning only when the repository owns that content type, since it will also delete pages of the same type that were created in the dashboard or from Obsidian. Combine it with dry-run to preview what would be deleted.

It always deploys. Every non-dry run ends by triggering a deploy, which counts against your monthly deploy quota. Use dry-run: 'true', for example on pull requests, to check changes without publishing.

Media is separate. Relative image references like ![](./photo.jpg) are not uploaded; the action warns about them. Upload images through the dashboard or the media API and reference them by URL.

The action source lives in the integrations repository. For the underlying API, see the pages and builds reference.