---
title: "Create short links in GitHub Actions · Flared"
description: "A complete GitHub Actions workflow that creates a short link for each release, adds UTM tags, survives re-runs, and writes the link into the release notes."
---

[Blog](https://flared.page/blog)

# Create short links in GitHub Actions

October 15, 2026, updated October 20, 2026

Install the `flared` CLI in the job, give it an API token in `FLARED_TOKEN`, and run `flared link create URL --json`. The workflow below makes a short link for each GitHub release, tags it with [UTM parameters](https://flared.page/blog/utm-parameters-short-links), and adds it to the release notes.

## What you need

- A Flared account. Free works and includes the API and the CLI.
- An API token. In the dashboard, open Connections, API tokens, and create one with the `links:write` scope. Add `domains:read` if links go on your own domain.
- The token as a repository secret named `FLARED_TOKEN`, under Settings, Secrets and variables, Actions.

## The workflow

Save this as `.github/workflows/release-link.yml`:

```
name: Release short link
on:
  release:
    types: [published]
permissions:
  contents: write
jobs:
  link:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm install -g @flaredlink/cli
      - name: Create the short link
        id: link
        env:
          FLARED_TOKEN: ${{ secrets.FLARED_TOKEN }}
          TAG: ${{ github.event.release.tag_name }}
          URL: ${{ github.event.release.html_url }}
        run: |
          slug="release-$(echo "$TAG" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9\n' '-')"
          short=$(flared link create "$URL" --slug "$slug" \
            --idempotency-key "release-${{ github.event.release.id }}" \
            --utm-source github --utm-medium release --utm-campaign "$slug" \
            --json | jq -r '.link.shortUrl')
          echo "url=$short" >> "$GITHUB_OUTPUT"
          echo "Short link: $short" >> "$GITHUB_STEP_SUMMARY"
      - name: Add it to the release notes
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ github.event.release.tag_name }}
        run: |
          notes=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json body -q .body)
          gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" \
            --notes "$notes"$'\n\n'"Share this release: ${{ steps.link.outputs.url }}"
```

When you publish a release such as `v2.4.0`, the job:

1. Installs the CLI (`npm install -g @flaredlink/cli`), which needs Node.js 20 or later.
2. Makes a slug from the tag, `release-v2-4-0`: Flared slugs use lowercase letters, digits, and hyphens.
3. Creates the link to the release page, with `utm_source=github`, `utm_medium=release`, and the slug as `utm_campaign` on the destination.
4. Reads the short address from the JSON output, writes it to the job summary, and appends it to the release notes with the GitHub CLI.

## Re-runs do not make a second link

A job that fails after the create step will run again, and a create that runs twice must not fail on its own slug. The `--idempotency-key` option handles this: for 24 hours, a create with the same key and the same input returns the link from the first run instead of a new one. The key here is the release ID, which stays the same across re-runs.

After 24 hours, a second create with the same slug fails with `SLUG_TAKEN` and exit code 6. Flared keeps every slug reserved, even after you turn a link off, so an old short address never points somewhere new.

## Exit codes for your scripts

The CLI returns stable exit codes, so a step can react to the reason for a failure: 0 success, 3 not signed in, 4 not allowed, 6 rejected input such as a taken slug, 7 plan limit, 8 rate limited, and 9 service unavailable. With `--json`, the error goes to standard error as `{"error": {"code", "message", "requestId"}}`.

## Use your own domain

Add `--domain` with a subdomain that you set up in Flared, such as `go.example.com`. The CLI checks that the domain is active before it creates the link, which needs the `domains:read` scope on the token.

```
flared link create "$URL" --slug "$slug" --domain go.example.com --json
```

See [custom domains](https://flared.page/docs#domains) for the CNAME record. Free includes 5 custom domains.

## Other ways to call Flared

- Without Node.js, call the [REST API](https://flared.page/docs#api) with `curl`. Send an `Idempotency-Key` header; the API requires one on every create.
- Read clicks from the same job with `flared analytics release-v2-4-0 --json`.
- For a self-hosted Flared, set `FLARED_API_URL` to its API address, such as `https://links.example.com/v1`.
- To ask for links in a chat instead, connect an AI assistant to the [MCP server](https://flared.page/docs/assistants).

[Start free](https://flared.page/app/login?mode=signup) [More posts](https://flared.page/blog)
