SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Author and maintain actions

Share, Publish and Version GitHub Actions (GH-200)

17 min readGH-200 · Author and maintain actionsUpdated

Distributing a GitHub Action means choosing how other workflows reach it — from a public repository, from a private or internal repository shared through its Actions Access setting, or as a public listing on GitHub Marketplace — and then releasing it so consumers get fixes without breaking changes. This lesson covers the GitHub Actions (GH-200) skills for distribution models and uses: reference forms, private and enterprise sharing and their caller rules, Marketplace requirements, naming, publishing, delisting and transfer, and versioning with semantic release tags, moving major tags, release branches, consumer ref choices, immutable releases and release packaging.

What you’ll learn
  • Choose between public, private/internal and Marketplace distribution for an action given who must run it and who may see the code.
  • Write the correct uses: reference for root, subfolder and same-repository actions, including when ./ needs a checkout and what $/ resolves to.
  • Share a private or internal action with the right Access option and explain which caller repositories it admits.
  • List the GitHub Marketplace requirements and naming rules, and publish, delist or transfer a Marketplace action.
  • Apply semantic version tags, moving major tags and release branches for compatible and breaking changes across several major versions, including moving the major tag from a workflow.
  • Compare major tags, exact tags, full SHAs and branches as consumer references, and adapt the release process to immutable releases.
  • Package a JavaScript or Docker action release so it runs for consumers on every GitHub platform.

The three distribution models: public, private and Marketplace

A custom action reaches other workflows in one of three ways, and the choice decides who can run it and who can see its code. A public repository makes the action usable by any workflow on GitHub. A private or internal repository keeps the code hidden and shares the action only with workflows that the repository's Access setting admits. GitHub Marketplace is not a separate hosting location: it is a public listing, published from a release, layered on top of a public repository to make the action discoverable.

Public repositoryPrivate / internal repositoryGitHub Marketplace
Who can run itAny workflow, with owner/repo@refOnly workflows the Access option admits (same user, organization or enterprise)Anyone — it is a public repository
Who can see the codeEveryoneOnly people with access to the repository (internal: enterprise members)Everyone
How you enable itNothing to do — make the repository publicSettings > Actions > General > Access on the action's repositoryPublish a release with the Marketplace option ticked
Best forOpen-source actionsCompany-internal actionsOpen-source actions you want people to find

Two choices sit outside these models and are usually the wrong answer when an action must be shared: copying the action's files into each repository, and forking it. Both freeze the code — consumers never receive the maintainer's later fixes — and a fork of a private action is unusable elsewhere until its own Access option is set.

This lesson covers distribution, Marketplace publishing and release strategy. Writing the metadata file and the action's code is covered in the sibling lesson on creating and troubleshooting custom actions; enterprise allow-lists that decide which actions may run are an enterprise governance topic.

Referencing an action: owner/repo@ref, subfolders, ./ and $/

A workflow step runs an action by naming where it lives in uses:, and the form depends on whether the action is in another repository or in the workflow's own repository.

  • Another repository, metadata at the root: {owner}/{repo}@{ref}, for example octo-org/lint-action@v1. Any public repository can be used this way, with no Marketplace listing needed.
  • Another repository, action in a subfolder: {owner}/{repo}/{path}@{ref}, for example octo-org/tools/actions/lint@v2.0.0. The path names the folder that holds action.yml, never the file itself, and the @ref always comes last. The ref can be a tag, a branch or a full commit SHA.
  • Same repository, ./path: ./.github/actions/setup is a path relative to the job's workspace. The workspace is empty until the repository is checked out, so a ./ action needs an actions/checkout step first; without it the runner cannot find the metadata file. A ./ path can never reach another repository's action.
  • Same repository, $/path: the self-repository reference $/.github/actions/setup resolves to the workflow's own repository at the commit that is running and does not need a checkout step. It is a recent GitHub.com feature and is not available on GitHub Enterprise Server.

Pointing a step at your own repository with my-org/app/.github/actions/setup@main also works, but it runs whatever main holds when the job starts, which may not be the commit that triggered the run. A value without ./ or $/ and without an owner, such as .github/actions/setup, is invalid.

steps:
  - uses: actions/checkout@v5
  - uses: ./.github/actions/setup        # needs the checkout above
  - uses: $/.github/actions/setup        # running commit, no checkout
  - uses: octo-org/tools/actions/lint@v2.0.0

Sharing a private or internal action: the Access setting and caller rules

An action in a private repository is usable only by that repository's own workflows until the repository's Access option is changed, under Settings > Actions > General > Access on the action's repository. That setting — not people's repository permissions — is what lets workflows in other repositories load the action, and the repository stays private.

The options offered depend on who owns the repository:

Action repositoryAccess optionsWhich callers it admits
Private, owned by a userNot accessible · Accessible from repositories owned by the userThe same user's other private repositories
Private, owned by an organizationNot accessible · organization · enterprise (when the organization is in an enterprise)Private repositories only
Internal (enterprise organizations)Not accessible · organization · enterprisePrivate or internal repositories

The general rule: only the Access setting on the action's own repository opens it to other repositories' workflows. Three other mechanisms are often confused with it. Repository roles govern what people can do, not which workflows can load code, so a user's or team's permissions never decide whether a workflow can use the action. Credentials are irrelevant because the runner resolves uses: references itself rather than with a token supplied by the calling workflow. And policies on which actions are allowed to run only restrict use; they never expose a private repository that has not been shared.

Sharing is scoped to one owner. A user-owned private repository can never admit an organization's repositories, however it is configured; to serve an organization, the repository has to be transferred into that organization and then shared with the organization option.

Which callers a shared action admits

A shared non-public action serves non-public callers only. An action in a private repository can be used only from private repositories, and an action in an internal repository can be used from private or internal repositories. A public repository cannot use either, even inside the same organization and even when the organization's policy allows all actions — the step fails because the action cannot be loaded. If public repositories need the action, the action itself must be public.

The enterprise option is how one action reaches several organizations. Put the action in an internal repository and choose "Accessible from repositories in the enterprise": internal visibility keeps the code visible only to enterprise members, and private or internal workflows in every organization of the enterprise can use it. A private repository can also offer the enterprise option, but it still admits only private callers, so internal repositories in other organizations stay excluded. The organization option reaches only the organization that owns the repository.

One side effect to remember: GitHub warns that outside collaborators on a consuming repository can indirectly see the private action's behaviour through the logs of runs that use it. Sharing the action does not give them the repository, but run logs are visible to anyone who can see the consuming repository's workflow runs.

GitHub Marketplace listing requirements and naming rules

To be listed on GitHub Marketplace, an action must live in a public repository that contains a single action metadata file at the root (action.yml or action.yaml — both names are accepted), and the action's name must be unique. Private and internal repositories cannot be listed; no Access setting changes that.

Marketplace lists one action per repository. Actions in subfolders of a repository remain usable with owner/repo/path@ref, but they are not listed. To publish several actions as separate listings, give each its own public repository with its metadata file at the root — which also lets each be versioned and released independently, as GitHub recommends for actions meant for other people. A root composite action that wraps the others produces one listing, not several.

The listing's name comes from the name field in the metadata file — not the repository name and not the release title. That name must not match:

  • an action already published on Marketplace;
  • a GitHub user or organization, unless that same user or organization is the one publishing (a publisher may use its own account name);
  • an existing Marketplace category, such as a category named for deployment or code quality;
  • a name GitHub reserves for its own features.

Containing a word is not the same as matching: a name such as acme-deploy-helper is fine, because it matches no account, category or feature name. A name clash is fixed by changing name in the metadata file; choosing a different category or renaming the repository does not help.

Publishing an action to GitHub Marketplace

An action is published to Marketplace by publishing a release with the Marketplace option ticked; there is no settings switch and no separate submission. The flow is:

  1. Open the action's metadata file in the repository; GitHub shows a banner offering to Draft a release.
  2. Select Publish this Action to the GitHub Marketplace. GitHub checks the metadata and shows any warnings (for example a missing description or branding) to fix.
  3. Choose a primary category (and optionally a secondary one).
  4. Enter a version tag (a semantic version such as v1.0.0) and a release title, then publish. Publishing requires two-factor authentication on your account.

Before the checkbox can be used, the account that owns the repository must accept the GitHub Marketplace Developer Agreement. Until then the checkbox is disabled. For an organization-owned repository only an organization owner can accept it; so a repository admin who is not an owner has to ask an owner to accept it.

GitHub does not review actions before they appear: an action that meets the requirements is listed as soon as the release is published. Pushing a tag on its own lists nothing.

The verified creator badge is optional. It marks actions from partner organizations that GitHub has verified; partners request it by contacting partnerships@github.com. It is not needed to publish.

Branding, delisting and transferring a Marketplace action

The optional branding key in the metadata file sets the badge Marketplace shows for the action: branding.icon takes an icon name from the Feather icon set, and branding.color takes one of a fixed set of background colours (such as white, black, yellow, blue, green, orange, red, purple or gray-dark). Branding is cosmetic: it does not make a repository eligible, and runs, inputs and description do not set the badge.

name: 'acme-deploy-helper'
description: 'Deploy the build output to a staging environment'
branding:
  icon: 'check-circle'
  color: 'green'

Removing a listing is also done through releases. Each release carries its own Marketplace publishing flag, so you edit every release that was published to Marketplace and clear the checkbox; clearing it on the latest release alone leaves older releases listed. The releases, tags and public repository stay, so workflows that reference the action keep working. Deleting tags or making the repository private would delist it too, but would break every consumer.

Transferring the repository to another owner moves its releases, tags and listing with it, and GitHub creates a redirect from the old URL, so existing uses: old-owner/repo@ref references keep resolving. Tags are not moved or rewritten. If the new owner is not a verified creator, the listing loses the verified creator badge; republishing releases cannot restore it.

Versioning and release strategy: semantic tags and major tags

GitHub's recommended release management for actions combines semantic version release tags (v1.4.2) with a moving major version tag (v1) that always points at the latest compatible release. Users who reference @v1 receive bug fixes and security patches automatically, without editing their workflows.

  • Compatible change (bug fix, new optional input, internal improvement): release v1.4.3 or v1.5.0, then move v1 to that release's commit (a force-push of the tag).
  • Breaking change (renaming or removing an input, changing outputs or behaviour existing workflows rely on): release v2.0.0, create a new v2 tag, and leave v1 on its last release so existing workflows keep running until they choose to upgrade. Moving v1 to the breaking release, or calling it a patch, breaks every @v1 user.
  • Never delete a major tag that users reference; their workflows fail at that step.

The order matters. GitHub recommends developing and validating on a release branch (for example release/v1), creating the release with its semantic version tag, and only then moving the major tag. The major tag moves last, so users only ever receive validated, released code — never work in progress, and never the default branch, which GitHub advises users not to reference.

Moving the major tag from a workflow is a ref update, so it depends on token scope and on Git's rules for existing tags. Tags are repository contents: the job's GITHUB_TOKEN needs permissions: contents: write to create or update one. The actions scope governs workflow runs (for example cancelling them), not refs. Git also protects existing remote tags: retagging in the runner changes only the clone's copy, and the remote accepts the new position only when the push is forced (for example git push --force origin v1). A typical automation runs on the release event (type published), retags the major version at the released commit and force-pushes it — and with immutable releases enabled, that works only while v1 is a plain tag with no release attached.

Maintaining several majors in parallel: when v1 lives on release/v1 and v2 on main, a fix both lines need is applied to each branch, each line gets its own patch release (v1.8.3, v2.1.1), and each major tag moves only within its own line. Pointing v1 at a v2 release gives v1 users breaking changes; pointing v2 at a v1 release takes v2 features away.

Which ref should a consumer use?

The ref a consumer writes after @ decides whether they receive updates and whether the code can change underneath them.

ReferenceReceives updates?Can the code change?Use it when
Major tag @v2Yes — every compatible releaseYes, by design (within the major)You want patches automatically without breaking changes
Exact tag @v2.3.1NoOnly if the maintainer force-moves the tag (impossible for an immutable release's tag)You want a fixed version and upgrade deliberately
Full commit SHANoNo — a SHA names one commitYou need exactly the same code on every run
Branch @main, @release/v2Yes — every pushYes, including unreleased and breaking workRarely; GitHub advises against it for consumers

When pinning by commit, GitHub requires the full 40-character SHA; an abbreviated SHA such as a1b2c3d is not accepted, and a comment naming the version beside it does not change that. An ordinary tag is a name that can be moved, so it does not pin a commit — except the tag of an immutable release, which is locked to its commit and whose name can never be reused. Using SHA pinning as a supply-chain security control is covered in the security domain; here the point is the trade-off between stability and receiving fixes.

Immutable releases and how they change the release process

Immutable releases are a repository or organization setting that, once enabled, makes each newly published release unchangeable: its Git tag is locked to its commit and cannot be moved or deleted while the release exists, and its assets cannot be added, replaced or deleted. The release title, release notes and the pre-release/latest flags stay editable. If the release is deleted, its tag can then be deleted, but the tag name can never be reused.

  • Future releases only: enabling or enforcing the setting does not change releases published earlier; they stay mutable (as stated in GitHub's immutable-releases announcement).
  • Draft, attach, publish: a draft release is still mutable, so a release workflow must create the release as a draft, upload every asset, then publish it. Uploading after publishing fails whatever token is used — the block is immutability, not permissions — and a published pre-release is locked too. Workflow artifacts linked from release notes are not release assets.
  • Major tags must stay plain tags: because a release locks its tag, the moving major tag (v1, or a minor tag like v1.1) must not have a GitHub release attached. Create releases only for specific versions (v1.0.1, v1.1.0) and keep the tags you need to move as plain Git tags that you force-push to the latest compatible commit. Once a release has locked a major tag, release settings cannot unlock it, and because a deleted release's tag name can never be reused, the practical recovery is to move consumers to a new tag name; a branch sharing the tag's name does not help, since it only makes the reference ambiguous.

For consumers, the tag of an immutable release is as stable as a full SHA, while a plain tag (including one pushed without a release) can still be repointed.

Packaging a release so it runs for consumers

The runner downloads the action's repository at the referenced ref and runs it as it is — it never installs dependencies or builds code for a JavaScript action. Everything the action needs must be in the commit the tag points at.

  • JavaScript actions must ship their dependencies. The maintainer's CI running npm ci hides the problem; consumers then fail with errors such as "Cannot find module '@actions/core'". GitHub suggests bundling the code and its dependencies into one file with a tool such as rollup.js or @vercel/ncc (rather than committing node_modules), pointing runs.main at the bundle (for example dist/index.js), and making sure the bundle is committed — not git-ignored — in the tagged commit. Changing runs.using (for example to node24) never makes the runner install packages, and installing them in a pre script moves the install into every consumer job.
  • Docker container actions with runs.image: Dockerfile are built on every run. To make runs faster without consumers changing anything but the release they reference, publish a prebuilt, versioned image per release and set runs.image to a docker:// reference (for example docker://ghcr.io/org/tool:1.2.0). A pre-entrypoint runs inside the container, so it cannot avoid the build.
  • Portability across GitHub platforms: do not hard-code https://api.github.com. Read the REST endpoint from GITHUB_API_URL (and GraphQL from GITHUB_GRAPHQL_URL), or use a toolkit such as @actions/github that does so, so the action works on GitHub.com, GHE.com and GitHub Enterprise Server. GITHUB_SERVER_URL is the web address, not the API, and a new required input would burden every user.

Tip. Expect scenario items that state who must run an action and who may see its code, then ask which distribution model, Access option or repository visibility fits — including why a public, internal or other-organization caller is refused, and what a user-owned versus organization-owned repository can offer. Reference items show a short YAML step and ask which uses: value works for a root, subfolder or same-repository action, with constraints such as running the triggering commit or adding no step. Marketplace items test eligibility, which name values are rejected, why the publish checkbox is disabled, how listing and delisting work through releases, and what a repository transfer keeps or loses. Versioning items describe a fix or a breaking change and ask how to release it and which tags to move, what a workflow that moves the major tag needs (token permission and push type), how to serve two major versions, which consumer reference meets a stability or update requirement, how immutable releases change asset uploads and major tags, and how to package JavaScript or Docker releases so they run for consumers.

Key takeaways
  • Any workflow can use an action in a public repository with owner/repo@ref; Marketplace only adds discovery.
  • Private and internal actions are shared through Settings > Actions > General > Access on the action's repository — not people's permissions, caller tokens or allow lists.
  • Private actions admit only private callers, internal actions admit private or internal callers, and public repositories can use neither.
  • To reach several organizations while staying inside the enterprise, use an internal repository with the enterprise Access option.
  • ./ paths need a checkout; $/ uses the running commit without one; subfolder actions use owner/repo/path@ref with the ref last.
  • Marketplace needs a public repository with one root metadata file and a unique name that matches no other action, other account, category or GitHub feature.
  • Publish to Marketplace by publishing a release with the Marketplace option; the owner must accept the Developer Agreement, 2FA is required, and there is no review.
  • Delist by clearing the Marketplace option on every published release; a transfer keeps uses: working through a redirect but drops the verified badge for an unverified owner.
  • Release vX.Y.Z from a validated release branch, then move vX; breaking changes get a new major and the old major tag stays put.
  • Immutable releases lock tags and assets for future releases: draft, attach assets, publish — and keep moving major tags as plain tags without releases.
  • Automating the major tag move needs contents: write on the GITHUB_TOKEN and a forced push (git push --force origin v1), because the tag already exists on the remote.

Frequently asked questions

Do I need to publish an action to GitHub Marketplace for others to use it?

No. Any workflow can use an action from a public repository by referencing it as owner/repo@ref (or owner/repo/path@ref for an action in a subfolder). Marketplace is an optional public listing that helps people discover the action; it does not change who can run it.

How do I share a GitHub Action from a private repository with my organization?

On the action's repository, open Settings > Actions > General and set the Access option to allow repositories in the organization. Workflows in the organization's private repositories can then use the action with owner/repo@ref while the code stays private. Public repositories still cannot use it, and giving people Read access to the repository does not grant workflows access.

Can an internal repository's action be used across all organizations in an enterprise?

Yes. Setting an internal repository's Access option to the enterprise lets private and internal repositories in every organization of the enterprise use its actions, while internal visibility keeps the code visible only to enterprise members. A private repository with the enterprise option admits only private repositories.

What are the requirements for publishing an action to GitHub Marketplace?

The action must be in a public repository with a single action metadata file (action.yml or action.yaml) at the root, and its name must be unique: it cannot match an existing Marketplace action, a Marketplace category, a GitHub feature name, or another user or organization unless that owner is publishing. The owning account must accept the Marketplace Developer Agreement and the publisher needs two-factor authentication.

How do I remove an action from GitHub Marketplace without breaking existing workflows?

Edit every release that was published to Marketplace and clear its publishing checkbox. The repository, releases and tags stay, so workflows that reference the action keep working. Deleting tags or making the repository private would also break those workflows.

How should GitHub Actions be versioned?

GitHub recommends semantic version release tags such as v1.4.2 plus a major version tag such as v1 that is moved to each compatible release after it is validated on a release branch. Changes that break existing workflows, such as renaming an input, get a new major version (v2) while the old major tag stays on its last release.

How do immutable releases affect moving major version tags like v1?

An immutable release locks its tag, so a major tag that has a release attached can no longer be moved, and its name cannot be reused even after the release is deleted. Create releases only for specific versions such as v1.0.1, and keep major tags as plain Git tags without a release so they can still be moved to the latest compatible commit.

Why does my JavaScript action fail for users with Cannot find module?

The runner runs the action's files exactly as they are at the referenced tag and does not install dependencies. Bundle the code and its dependencies into one file with a tool such as rollup.js or @vercel/ncc, point runs.main at the bundle and commit it in the tagged release.

Source

This lesson covers the "Author and maintain actions" domain of the official GH-200 exam guide. Vendors revise their guides — check the source for the current version.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?