Extension Catalogs
Extension catalogs control where specify extension search and add look for
extensions. A catalog stack is an ordered list of sources searched together.
Lower catalog priority numbers take precedence when the same extension ID
appears in more than one source.
Trust model: discovery-only vs. install sources
Catalogs come in two kinds, and the distinction is a security boundary:
- Install sources (
install_allowed: true) are catalogs you trust as a place to install from. The built-indefaultcatalog is one, as is a catalog you author and vet yourself. - Discovery-only catalogs (
install_allowed: false) are searchable surfaces for finding extensions, but are not installable. The built-incommunitycatalog is already active for search; you do not need to add it.
| Priority | Built-in catalog | Install allowed | Purpose |
|---|---|---|---|
| 1 | catalog.json (default) |
Yes | Installable extensions |
| 2 | catalog.community.json (community) |
No | Discover community extensions |
Community entries are unvetted third-party code. Maintainers check catalog metadata, not extension code. You must vet an extension before installing it.
Warning
Do not flip an unvetted discovery-only catalog to install_allowed. That
defeats the separation between discovery and installation.
There are two ways to install something found through the community catalog:
Install a single vetted release directly. Get the candidate archive URL from
specify extension info <name>, which prints a "Candidate archive" URL for uninstalled discovery-only entries. Treat the URL as untrusted until you have reviewed that release archive, then install it:specify extension info <name> specify extension add <name> --from <archive-url>Curate your own catalog. For a governed, reusable organization source, review the releases yourself and mark only that controlled catalog
install_allowed: true.
Catalog resolution order
The active stack is selected in this order:
- Environment variable:
SPECKIT_CATALOG_URLoverrides the entire stack. - Project config:
.specify/extension-catalogs.yml. - User config:
~/.specify/extension-catalogs.yml. - Built-in defaults:
defaultandcommunity.
An existing project configuration takes precedence over user configuration and
built-in defaults. Empty or malformed files, including catalogs: [], fail
closed with a validation error rather than falling back to another source.
Remove the configuration file to use the next source in the resolution order,
or populate it with valid catalog entries.
Within the selected stack, catalogs are checked by priority; the first matching
extension ID wins. Exact-version lookup does not fall back to another catalog
when the winning entry lacks the requested version.
Managing catalogs
specify extension catalog list
specify extension catalog --help
# Add an internal source you own and vet
specify extension catalog add \
--name "internal" \
--priority 2 \
--install-allowed \
--description "Our approved extensions" \
https://internal.company.com/spec-kit/catalog.json
# Add a discovery-only source (the default permission)
specify extension catalog add \
--name "partner" \
--priority 5 \
https://partner.example.com/spec-kit/catalog.json
specify extension catalog remove internal
These commands edit the project's .specify/extension-catalogs.yml.
Re-adding the same named catalog with identical settings succeeds without
changing configuration; different settings are rejected. The complete option
reference is in Catalog management.
You can also edit the configuration directly:
catalogs:
- name: "default"
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json"
priority: 1
install_allowed: true
description: "Built-in catalog of installable extensions"
- name: "internal"
url: "https://internal.company.com/spec-kit/catalog.json"
priority: 2
install_allowed: true
description: "Our approved extensions"
- name: "community"
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json"
priority: 3
install_allowed: false
description: "Community-contributed extensions (discovery only)"
Organization catalog customization
Organizations use private catalogs to control available releases, integrate internal tools, meet compliance requirements, or support air-gapped networks.
Create a catalog
Create a catalog.json file containing the releases your team has vetted:
{
"schema_version": "1.0",
"updated_at": "2026-02-03T00:00:00Z",
"catalog_url": "https://your-org.com/spec-kit/catalog.json",
"extensions": {
"jira": {
"name": "Jira Integration",
"id": "jira",
"description": "Create Jira issues from spec-kit artifacts",
"author": "Your Organization",
"version": "2.1.0",
"download_url": "https://github.com/your-org/spec-kit-jira/archive/refs/tags/v2.1.0.zip",
"repository": "https://github.com/your-org/spec-kit-jira",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0",
"tools": [
{"name": "atlassian-mcp-server", "required": true}
]
},
"provides": {
"commands": 3,
"hooks": 1
},
"tags": ["jira", "atlassian", "issue-tracking"],
"verified": true
},
"internal-tool": {
"name": "Internal Tool Integration",
"id": "internal-tool",
"description": "Connect to internal company systems",
"author": "Your Organization",
"version": "1.0.0",
"download_url": "https://internal.your-org.com/extensions/internal-tool-1.0.0.zip",
"repository": "https://github.internal.your-org.com/spec-kit-internal",
"license": "Proprietary",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 2
},
"tags": ["internal", "proprietary"],
"verified": true
}
}
}
Host and configure the catalog
| Method | URL example | Use case |
|---|---|---|
| GitHub Pages | https://your-org.github.io/spec-kit-catalog/catalog.json |
Public or org-visible |
| Internal web server | https://internal.company.com/spec-kit/catalog.json |
Corporate network |
| S3/cloud storage | https://s3.amazonaws.com/your-bucket/catalog.json |
Cloud-hosted teams |
| Local file server | http://localhost:8000/catalog.json |
Development/testing |
URLs must use HTTPS, except localhost for testing.
Add your catalog with specify extension catalog add, or use:
# .specify/extension-catalogs.yml
catalogs:
- name: "my-org"
url: "https://your-org.com/spec-kit/catalog.json"
priority: 1
install_allowed: true
description: "Our approved extensions"
For CI/CD or a single-catalog environment, replace the whole stack:
export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json"
Check the result:
specify extension catalog list
specify extension search
specify extension add jira
Private GitHub-hosted catalogs
GH_TOKEN or GITHUB_TOKEN authenticates requests to GitHub-hosted URLs
(raw.githubusercontent.com, github.com, api.github.com, and
codeload.github.com). Private catalog JSON and extension ZIPs require access
to the relevant repositories. Non-GitHub catalog URLs are fetched without
these credentials.
export GITHUB_TOKEN=$(gh auth token)
specify extension search jira
specify extension add jira-sync
Curated and air-gapped environments
A catalog can list only the approved extensions, such as jira and github.
Configure it as the sole catalog if only those entries should appear in search.
Private/proprietary extensions can point to internal archive hosts; public
catalog submission is optional.
For air-gapped networks, download vetted extension ZIPs to an internal file
server, create a catalog pointing to those internal URLs, and host the catalog
inside the network. For example, an entry can use
"download_url": "https://files.internal/spec-kit/jira-2.1.0.zip".
Development and testing
python -m http.server 8000 --directory ./my-catalog/
In another terminal:
export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json"
specify extension add my-new-extension
Catalog configuration does not prevent direct installation:
specify extension add jira
specify extension add <extension-name> --from https://github.com/someone/spec-kit-ext/archive/refs/tags/v1.0.0.zip
specify extension add --dev /path/to/extension
Direct URL installation shows a security warning because the archive is not being selected through your configured catalog. Vet it before installing.
Catalog entry schema
The following describes the catalog fields; community submission requirements also require complete descriptive metadata.
The catalog root must contain schema_version and an extensions mapping.
Each mapping key is the authoritative extension ID; a repeated id field
inside the entry is optional.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable name |
id |
string | No | Optional repetition of the authoritative mapping key |
version |
string | Yes | Current extension version |
download_url |
string | For archive releases | URL to the release archive; bundled entries can omit it |
bundled |
boolean | No | Marks an extension shipped with Spec Kit, resolved from the running distribution |
repository |
string | No | Source code URL (required for community submission) |
description |
string | Yes | Brief description; community submissions require under 200 characters |
author |
string | No | Author/organization (required for submission) |
license |
string | No | SPDX license identifier (required for submission) |
homepage, documentation, changelog |
string | No | Related URLs |
sha256 |
string | No | Archive SHA-256 digest; verified before install when supplied |
requires.speckit_version |
string | No | Version constraint (required for submission) |
requires.tools |
array | No | Objects with required name, optional version, and required (default false) |
provides.commands, provides.hooks |
number | No | Capability counts |
tags |
array | No | Search tags (2-10 recommended) |
verified |
boolean | No | Metadata flag set by maintainers, default false; not a code-audit guarantee |
created_at, updated_at |
string | No | ISO 8601 timestamps |
releases |
object | No | Historical releases keyed by version |
Historical releases
Catalogs may keep the current release in the top-level fields and add older
releases in a releases mapping. Older single-version catalogs continue to
work unchanged. Each historical release needs its own download URL and SHA-256
digest; release-specific requirements or provided capabilities must be placed
in that release's record rather than inherited from the current one. As with
current releases, a digest may use a case-insensitive sha256: prefix and
surrounding whitespace.
{
"extensions": {
"my-extension": {
"name": "My Extension",
"version": "0.5.1",
"download_url": "https://example.com/my-extension-0.5.1.zip",
"sha256": "<64-character SHA-256 for 0.5.1>",
"releases": {
"0.4.12": {
"download_url": "https://example.com/my-extension-0.4.12.zip",
"sha256": "<64-character SHA-256 for 0.4.12>"
}
}
}
}
}
The example omits other metadata for brevity. The current version must not be
repeated in releases; malformed or duplicate release records are rejected.
Equivalent PEP 440 spellings, such as v1.0 and 1.0, select the same release;
the catalog's advertised spelling remains visible.
Catalog-backed bundle extension pins also select historical releases from the winning source, using that release's own archive and digest and checking the downloaded manifest's ID and version. Bundled extensions follow the packaged version policy: a newer compatible packaged patch can be used with a warning. See the bundle reference for bundle installation.