1---
2title: Repositories
3description: Manage GitHub repositories as Stack resources — create or adopt a repo, converge its settings on every deploy, rename in place, and opt into deletion explicitly.
4---
5
6`GitHub.Repository` manages the lifecycle of a repository owned by a
7user or organization. The repository is created on first deploy and
8its settings are converged on every subsequent deploy — description,
9visibility, tabs, merge policy, topics — so repo configuration lives
10in code next to the infrastructure it hosts.
11
12Repositories are treated as precious: destroying the stack retains
13the repository by default, and deleting it is an explicit opt-in.
14New here? [Set up credentials](/github/setup) first.
15
16## Create a repository
17
18```typescript
19// alchemy.run.ts
20import * as Alchemy from "alchemy";
21import * as GitHub from "alchemy/GitHub";
22import * as Effect from "effect/Effect";
23
24export default Alchemy.Stack(
25 "MyRepos",
26 {
27 providers: GitHub.providers(),
28 state: Alchemy.localState(),
29 },
30 Effect.gen(function* () {
31 const repo = yield* GitHub.Repository("api", {
32 owner: "my-org",
33 name: "api",
34 description: "API service",
35 autoInit: true,
36 });
37
38 return { url: repo.htmlUrl, clone: repo.cloneUrl };
39 }),
40);
41```
42
43`owner` is a user or organization login — alchemy picks the matching
44create endpoint automatically. Outputs include the stable numeric
45`repoId`, `fullName`, `htmlUrl`, the `sshUrl`/`cloneUrl`/`gitUrl`
46clone URLs, and the resolved `defaultBranch`.
47
48## Adopt an existing repository
49
50Pointing `Repository` at a name that already exists under the owner
51doesn't fail with a name conflict. The deploy observes the live
52repository and converges its settings in place, so you can bring an
53existing repo under management just by declaring it:
54
55```typescript
56yield* GitHub.Repository("api", {
57 owner: "my-org",
58 name: "api", // already exists — settings below are applied to it
59 hasWiki: false,
60 deleteBranchOnMerge: true,
61});
62```
63
64## Settings alchemy manages
65
66Every deploy converges the declared settings against the live
67repository:
68
69```typescript
70const repo = yield* GitHub.Repository("internal-tools", {
71 owner: "my-org",
72 name: "internal-tools",
73 visibility: "private",
74 hasWiki: false,
75 hasProjects: false,
76 deleteBranchOnMerge: true,
77});
78```
79
80- **`description`, `homepage`** — the repository page blurb and link.
81- **`visibility`** — `"public"` (default), `"private"`, or
82 `"internal"` (organization repos on GitHub Enterprise only).
83- **Tabs** — `hasIssues`, `hasProjects`, `hasWiki` (default `true`)
84 and `hasDiscussions` (default `false`).
85- **Merge policy** — `allowSquashMerge`, `allowMergeCommit`,
86 `allowRebaseMerge` (default `true`), `allowAutoMerge` and
87 `deleteBranchOnMerge` (default `false`).
88- **`isTemplate`** — mark the repository as a template.
89- **`defaultBranch`** — only applied once the branch actually exists;
90 on an empty repository it's dropped for that deploy and applied on
91 a later one.
92
93## Topics
94
95The `topics` list fully replaces whatever is on the repository —
96removing the prop on a later deploy clears them:
97
98```typescript
99yield* GitHub.Repository("sdk", {
100 owner: "my-org",
101 name: "sdk",
102 topics: ["typescript", "effect", "sdk"],
103});
104```
105
106## Seed the first commit
107
108`autoInit`, `gitignoreTemplate`, and `licenseTemplate` initialize the
109repository at create time — an empty README, a `.gitignore` template,
110and a license. They are only honored on creation; changing them on a
111later deploy has no effect on an existing repository:
112
113```typescript
114yield* GitHub.Repository("service", {
115 owner: "my-org",
116 name: "service",
117 autoInit: true,
118 gitignoreTemplate: "Node",
119 licenseTemplate: "mit",
120});
121```
122
123## Rename, don't replace
124
125Keep the same logical ID and change `name` to rename the live
126repository in place — history, issues, and pull requests are
127preserved, and the numeric `repoId` stays the same:
128
129```typescript
130// First deploy creates "api"; this later deploy (same logical ID)
131// renames it to "gateway".
132const repo = yield* GitHub.Repository("api", {
133 owner: "my-org",
134 name: "gateway",
135});
136```
137
138Only changing `owner` triggers a replacement — a repository can't be
139moved between owners by an update.
140
141## Archive a repository
142
143`archived: true` makes the repository read-only; set it back to
144`false` on a later deploy to un-archive:
145
146```typescript
147yield* GitHub.Repository("legacy", {
148 owner: "my-org",
149 name: "legacy-service",
150 archived: true,
151});
152```
153
154## Deleting a repository
155
156Destroying the stack does **not** delete the repository — the default
157removal policy is retain, protecting irreplaceable history. Opt into
158actual deletion with `destroy()`:
159
160```typescript
161import { destroy } from "alchemy/RemovalPolicy";
162
163yield* GitHub.Repository("ephemeral", {
164 owner: "my-org",
165 name: "ephemeral-preview",
166}).pipe(destroy());
167```
168
169The token needs the `delete_repo` scope for this (plain `repo` scope
170covers everything else). Deletion resolves the repository by its
171stable `repoId`, so it targets the right repo even after a rename.
172
173## Escape hatch: Octokit
174
175For anything the resource model doesn't cover, the
176`GitHubCredentials` service hands you an authenticated
177[Octokit](https://github.com/octokit/rest.js) client — the same one
178every GitHub resource uses:
179
180```typescript
181import * as GitHub from "alchemy/GitHub";
182import * as Effect from "effect/Effect";
183
184const program = Effect.gen(function* () {
185 const creds = yield* yield* GitHub.GitHubCredentials;
186 const octokit = creds.octokit();
187
188 const { data } = yield* Effect.tryPromise(() =>
189 octokit.rest.repos.get({ owner: "my-org", repo: "api" }),
190 );
191 return data;
192});
193```
194
195(`GitHubCredentials` resolves to an effect that yields the service,
196hence the double `yield*`.)
197
198Inside a stack, `GitHub.providers()` already carries
199`GitHubCredentials`, resolved via [the login flow](/github/setup).
200Outside one, build the layer directly:
201
202- **`GitHub.fromEnv()`** — reads `GITHUB_ACCESS_TOKEN` or
203 `GITHUB_TOKEN`.
204- **`GitHub.fromToken(token)`** — a literal token, plain string or
205 `Redacted` — useful in tests.
206
207```typescript
208program.pipe(Effect.provide(GitHub.fromEnv()));
209```
210
211## Where next
212
213Related:
214
215- [Actions secrets & variables](/github/actions-config) — seed the
216 CI configuration your repository's workflows read.
217- [Repository events](/github/events) — subscribe a Worker to the
218 repository's webhooks with typed payloads.
219- [CI/CD guide](/environments/ci) — deploy the stack itself from GitHub
220 Actions.
221
222Reference:
223
224- [Repository API reference](/providers/github/repository)