Publish an HTML file or a built folder to a private, unguessable Stacktree URL from any workflow. Use it to share a build artifact, a report, or a preview without a deploy pipeline.
It is a composite action (bash + curl), so there is no build step and no Node dependency. It calls the same REST endpoint documented at stacktr.ee/openapi.json.
If you have a coding agent open, hand it this line and it does the rest — installs, verifies the connection, and learns the tool surface:
Fetch and follow the setup instructions at https://stacktr.ee/prompt.md
Works in any agent that can fetch a URL. The instructions are plain Markdown; read them first if you like.
Anonymous publish (no account, link lives 24 hours):
- uses: stevysmith/stacktree-actions@v1
with:
file: dist/report.htmlAuthenticated, with the URL kept stable across runs (replace-in-place):
- uses: stevysmith/stacktree-actions@v1
id: publish
with:
dir: storybook-static # a folder containing index.html
api-key: ${{ secrets.STACKTREE_API_KEY }}
site-id: ${{ vars.STACKTREE_SITE_ID }} # omit on first run, then pin the returned id
- run: echo "Published to ${{ steps.publish.outputs.url }}"| Input | Required | Description |
|---|---|---|
file |
one of file/dir |
Path to a single .html, .htm, or .md file. |
dir |
one of file/dir |
Path to a folder with index.html at its root. The action zips it for upload. |
api-key |
no | Stacktree API key (stk_live_…). Omit for an anonymous 24h link. Store as a repo secret. Create one at app.stacktr.ee/api-keys. |
site-id |
no | Existing site id or slug to replace in place (keeps the same URL). Requires api-key. |
slug |
no | Requested public subdomain (makes the site public). Requires api-key. |
password |
no | Optional passcode gate. Anonymous publishes and paid plans only. |
client |
no | File the page under a client space, by name or slug (e.g. Acme Co). The space is created on first use. Requires api-key; ignored on an anonymous publish. Filing is free on every plan; giving that client their own address and portal starts on Solo. |
client-path |
no | Path segment for this page inside the client space, e.g. june-report. Derived from the page title when omitted. Applies when a site is created, not on a site-id replace. |
api-host |
no | Defaults to api.stacktr.ee; override only for self-hosted Stacktree. |
| Output | Description |
|---|---|
url |
Canonical URL of the published site. |
id |
Stacktree site id (pin it as site-id to replace in place next run). |
name: Preview
on: pull_request
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build # produces dist/
- uses: stevysmith/stacktree-actions@v1
id: preview
with:
dir: dist
api-key: ${{ secrets.STACKTREE_API_KEY }}
- uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview: ${{ steps.preview.outputs.url }}`
})- The published link is private by default: the URL is unguessable and acts as the credential. Add
password, or gate by email domain from the dashboard, for a second layer. The passcode works on an anonymous publish and on a paid plan (a free account gets a 402plan_password_not_available); the email-domain gate is paid-plan only. - Filing under a client. Pass
client: Acme Coand the page joins that client's space, alongside everything else you have delivered to them. Filing is free on every plan. On a paid plan the space can take the client's own address —acme.youragency.com— and one passcode that covers every page in it.clientis applied on create, and on asite-idreplace it is applied afterwards viaPATCH /sites/<id>, so a pinned workflow can adopt it without republishing. - The account's plan decides how long the page lives. Anonymous publishes last 24 hours. A key on the free plan publishes 3 pages in total (the count is lifetime, so deleting a page does not free the slot) and every page expires after 7 days, which will break a PR-preview workflow that runs more than three times. Paid plans have no expiry. A pinned
site-idreplace does not count as a new publish. Over a limit, the API returns HTTP 402 with aplan_*error code and the step fails with the response in the log. - For agent-native publishing (Claude Code, Cursor, Codex) use the MCP server instead:
npx stacktree-install. See stacktr.ee/mcp-publish-html.
Developed in the Stacktree monorepo at packages/github-action and mirrored here for the Marketplace listing. Open issues at stacktr.ee.