---
title: "Publishing modules"
description: "Link a GitHub repository, publish immutable module versions by pushing semver tags, and manage deprecation, restoration, and deletion."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.stackship.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Publishing modules

Publishing is VCS-only: module versions are ingested from an approved GitHub
repository when a semver tag is pushed. There is no direct tarball upload in
this release.

## Link a repository

Create a module and link a repository from the organization's GitHub App
installation:

```http
POST /v1/registry/modules
```

Creation requires `registry:write` plus `vcs:read`, and an
`Idempotency-Key` like every state-changing v1 operation. The repository
must belong to an active installation of the same organization.

| Field | Constraint |
| --- | --- |
| `name` | Lowercase letters, digits, and interior hyphens; 1–64 characters |
| `system` | The provider target; lowercase letters and digits, 1–32 characters |
| Repository | One module per repository |

A module publishes the repository root. Module subdirectories and monorepos
of modules are not supported in v1, so each repository can back exactly one
module and tag webhooks route unambiguously. The repository link can be
replaced later with `PUT /v1/registry/modules/{module_id}/source`, which
additionally requires `vcs:admin`.

## Publish by pushing tags

Pushing a semver tag to the linked repository publishes a version:

- `v1.2.3` and `1.2.3` are both accepted; the stored version drops the
  optional `v`;
- prerelease tags such as `v2.0.0-rc.1` are allowed; and
- non-semver tags are ignored silently.

The signed GitHub webhook records the tag and the exact commit it pointed
at, then a durable ingestion downloads the archive at that commit, validates
it, and commits the version with its archive SHA-256. The version appears in
the registry shortly after the push, with its repository, tag, commit SHA,
and digest recorded permanently. If the tag moves between delivery and
ingestion, the ingestion fails rather than publishing ambiguous bytes;
re-tagging produces a new ingestion attempt.

Archives are subject to the platform archive limits: 100 MB compressed,
500 MB expanded, and 50,000 entries, with links and unsafe paths rejected. A
root `README.md` up to 1 MiB is stored and shown as plain preformatted text.

Ingestion history — including failure codes for rejected tags — is visible
on the module page and through
`GET /v1/registry/modules/{module_id}/ingestions`.

## Sync tags

```http
POST /v1/registry/modules/{module_id}/actions/sync
```

Sync lists the repository's tags, filters to semver, and enqueues
ingestions for versions that are not yet published, newest 50 per
invocation. Use it to backfill existing tags when a repository is first
linked, and to recover versions whose webhook delivery was missed.

## Immutability

A published version never changes. Re-publishing an existing version with
different bytes is rejected and recorded as an integrity-violation audit
event; re-publishing with identical bytes converges idempotently without
creating a new version. Consumers that pin a version therefore always
receive the same digest-verified package.

## Deprecate and restore

```http
POST /v1/registry/module-versions/{version_id}/actions/deprecate
POST /v1/registry/module-versions/{version_id}/actions/restore
```

Deprecation takes a reason of up to 512 characters. A deprecated version
keeps resolving for existing pins and stays in the version listing, but the
dashboard and management API flag it with its reason. Restore returns the
version to published. Individual versions cannot be deleted.

## Delete a module

```http
DELETE /v1/registry/modules/{module_id}
```

Deletion removes the module and every version from discovery and download.
It is audited with the version count. Stored packages are removed
asynchronously after the rows are gone; there is no partial state in which
a deleted module still resolves.

## Permissions

| Permission | Grants | Default roles |
| --- | --- | --- |
| `registry:read` | Browse, resolve, and download modules | All members; managed plan credentials |
| `registry:write` | Create, link, sync, deprecate, restore, delete | Owners and admins |

## Audit events

Registry activity is recorded in the organization audit inventory:

- `registry.module.created`, `registry.module.source_updated`, and
  `registry.module.deleted`;
- `registry.module.version_published`,
  `registry.module.version_deprecated`, and
  `registry.module.version_restored`;
- `registry.module.version_downloaded`, deduplicated per credential and
  version within one hour;
- `registry.module.integrity_violation`; and
- `registry.module.sync_requested`.

Payloads carry public IDs, versions, and digests — never tokens or archive
contents.

## Related pages

- [Module registry](/registry) — addresses, consuming, and credentials
- [GitHub source connections](/workspaces/vcs) — the GitHub App installation
- [Audit events](/governance/audit)
- [v1 REST API](/automation/api) — idempotency and request rules

Source: https://docs.stackship.run/registry/publishing/index.mdx
